|
| 1 | +import {exec as execCb} from 'node:child_process' |
| 2 | +import {existsSync, mkdirSync, readdirSync, rmSync, symlinkSync} from 'node:fs' |
| 3 | +import {createRequire} from 'node:module' |
| 4 | +import {dirname, join, resolve} from 'node:path' |
| 5 | +import {promisify} from 'node:util' |
| 6 | + |
| 7 | +const exec = promisify(execCb) |
| 8 | + |
| 9 | +/** |
| 10 | + * Subdirectory within `tmp/` where the packed CLI is extracted. |
| 11 | + * Used by both this setup and `resolveBinaryPath()` to locate the binary. |
| 12 | + */ |
| 13 | +export const CLI_PACK_DIR = 'cli-packed' |
| 14 | + |
| 15 | +/** |
| 16 | + * Returns the absolute path to the directory containing the extracted packed CLI. |
| 17 | + * |
| 18 | + * The packed CLI is extracted into `tmp/cli-packed/package/` (the default |
| 19 | + * directory name that `tar` creates when extracting an npm tarball). |
| 20 | + */ |
| 21 | +export function getPackedCliPath(): string { |
| 22 | + return resolve(process.cwd(), 'tmp', CLI_PACK_DIR, 'package') |
| 23 | +} |
| 24 | + |
| 25 | +/** |
| 26 | + * Vitest global setup that packs `@sanity/cli` into a tarball, extracts it, |
| 27 | + * and symlinks `node_modules` from the workspace so the binary can run. |
| 28 | + * |
| 29 | + * This ensures E2E tests exercise the actual publishable artifact rather than |
| 30 | + * the raw workspace source, catching issues with the `files` field, lifecycle |
| 31 | + * scripts, and missing exports. |
| 32 | + * |
| 33 | + * Requires `@sanity/cli` (and its workspace deps) to be built beforehand. |
| 34 | + */ |
| 35 | +export async function teardown(): Promise<void> { |
| 36 | + const packDestination = resolve(process.cwd(), 'tmp', CLI_PACK_DIR) |
| 37 | + if (existsSync(packDestination)) { |
| 38 | + rmSync(packDestination, {force: true, recursive: true}) |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +export async function setup(): Promise<void> { |
| 43 | + // 1. Locate the @sanity/cli package in the workspace |
| 44 | + // (import.meta.resolve is not available in vitest's globalSetup runner) |
| 45 | + const require = createRequire(import.meta.url) |
| 46 | + const cliPackageJsonPath = require.resolve('@sanity/cli/package.json') |
| 47 | + const cliPackageDir = dirname(cliPackageJsonPath) |
| 48 | + |
| 49 | + // 2. Prepare the destination directory |
| 50 | + const packDestination = resolve(process.cwd(), 'tmp', CLI_PACK_DIR) |
| 51 | + |
| 52 | + // Clean any previous pack artifacts |
| 53 | + if (existsSync(packDestination)) { |
| 54 | + rmSync(packDestination, {force: true, recursive: true}) |
| 55 | + } |
| 56 | + mkdirSync(packDestination, {recursive: true}) |
| 57 | + |
| 58 | + // 3. Pack @sanity/cli → tarball |
| 59 | + // pnpm pack runs prepack (manifest:generate) and postpack (cleanup) hooks. |
| 60 | + // stdout includes lifecycle script output, so we find the .tgz by listing the directory. |
| 61 | + await exec(`pnpm pack --pack-destination "${packDestination}"`, { |
| 62 | + cwd: cliPackageDir, |
| 63 | + }) |
| 64 | + |
| 65 | + const tarballFile = readdirSync(packDestination).find((f) => f.endsWith('.tgz')) |
| 66 | + if (!tarballFile) { |
| 67 | + throw new Error(`No .tgz file found in ${packDestination} after pnpm pack`) |
| 68 | + } |
| 69 | + const tarballPath = join(packDestination, tarballFile) |
| 70 | + |
| 71 | + // 4. Extract the tarball → creates packDestination/package/ |
| 72 | + await exec(`tar xzf "${tarballPath}" -C "${packDestination}"`) |
| 73 | + |
| 74 | + // Clean up the tarball — no longer needed |
| 75 | + rmSync(tarballPath) |
| 76 | + |
| 77 | + // 5. Symlink node_modules from the workspace package so the CLI can |
| 78 | + // resolve its runtime dependencies |
| 79 | + const extractedDir = join(packDestination, 'package') |
| 80 | + const nodeModulesTarget = join(extractedDir, 'node_modules') |
| 81 | + |
| 82 | + if (!existsSync(nodeModulesTarget)) { |
| 83 | + symlinkSync(join(cliPackageDir, 'node_modules'), nodeModulesTarget) |
| 84 | + } |
| 85 | +} |
0 commit comments