diff --git a/package.json b/package.json index 5521066..2f75f23 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "devDependencies": { "@antfu/eslint-config": "^0.37.0", "@posva/prompts": "^2.4.4", + "@types/fs-extra": "^11.0.1", "@types/ini": "^1.3.31", "@types/node": "^18.15.11", "@types/which": "^3.0.0", @@ -58,6 +59,7 @@ "execa": "^7.1.1", "fast-glob": "^3.2.12", "find-up": "^6.3.0", + "fs-extra": "^11.1.1", "fzf": "^0.5.1", "ini": "^4.0.0", "kleur": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfd4dc9..40d623d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ devDependencies: '@posva/prompts': specifier: ^2.4.4 version: 2.4.4 + '@types/fs-extra': + specifier: ^11.0.1 + version: 11.0.1 '@types/ini': specifier: ^1.3.31 version: 1.3.31 @@ -34,6 +37,9 @@ devDependencies: find-up: specifier: ^6.3.0 version: 6.3.0 + fs-extra: + specifier: ^11.1.1 + version: 11.1.1 fzf: specifier: ^0.5.1 version: 0.5.1 @@ -843,6 +849,13 @@ packages: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true + /@types/fs-extra@11.0.1: + resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==} + dependencies: + '@types/jsonfile': 6.1.1 + '@types/node': 18.15.11 + dev: true + /@types/ini@1.3.31: resolution: {integrity: sha512-8ecxxaG4AlVEM1k9+BsziMw8UsX0qy3jYI1ad/71RrDZ+rdL6aZB0wLfAuflQiDhkD5o4yJ0uPK3OSUic3fG0w==} dev: true @@ -855,6 +868,12 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/jsonfile@6.1.1: + resolution: {integrity: sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==} + dependencies: + '@types/node': 18.15.11 + dev: true + /@types/mdast@3.0.10: resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} dependencies: diff --git a/src/commands/nr.ts b/src/commands/nr.ts index 95f114f..636fe19 100644 --- a/src/commands/nr.ts +++ b/src/commands/nr.ts @@ -12,15 +12,19 @@ runCli(async (agent, args, ctx) => { if (args[0] === '-') { if (!storage.lastRunCommand) { - console.error('No last command found') - process.exit(1) + if (!ctx?.programmatic) { + console.error('No last command found') + process.exit(1) + } + + throw new Error('No last command found') } args[0] = storage.lastRunCommand } - if (args.length === 0) { + if (args.length === 0 && !ctx?.programmatic) { // support https://www.npmjs.com/package/npm-scripts-info conventions - const pkg = getPackageJSON(ctx?.cwd) + const pkg = getPackageJSON(ctx) const scripts = pkg.scripts || {} const scriptsInfo = pkg['scripts-info'] || {} diff --git a/src/config.ts b/src/config.ts index 9cbd7d1..97b12d0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -44,9 +44,9 @@ export async function getConfig(): Promise { return config } -export async function getDefaultAgent() { +export async function getDefaultAgent(programmatic?: boolean) { const { defaultAgent } = await getConfig() - if (defaultAgent === 'prompt' && process.env.CI) + if (defaultAgent === 'prompt' && (programmatic || process.env.CI)) return 'npm' return defaultAgent } diff --git a/src/detect.ts b/src/detect.ts index 8851d8e..a6c6b70 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -10,10 +10,11 @@ import { cmdExists } from './utils' export interface DetectOptions { autoInstall?: boolean + programmatic?: boolean cwd?: string } -export async function detect({ autoInstall, cwd }: DetectOptions = {}) { +export async function detect({ autoInstall, programmatic, cwd }: DetectOptions = {}) { let agent: Agent | null = null let version: string | null = null @@ -38,7 +39,7 @@ export async function detect({ autoInstall, cwd }: DetectOptions = {}) { agent = 'pnpm@6' else if (name in AGENTS) agent = name - else + else if (!programmatic) console.warn('[ni] Unknown packageManager:', pkg.packageManager) } } @@ -50,7 +51,7 @@ export async function detect({ autoInstall, cwd }: DetectOptions = {}) { agent = LOCKS[path.basename(lockPath)] // auto install - if (agent && !cmdExists(agent.split('@')[0])) { + if (agent && !cmdExists(agent.split('@')[0]) && !programmatic) { if (!autoInstall) { console.warn(`[ni] Detected ${agent} but it doesn't seem to be installed.\n`) diff --git a/src/fs.ts b/src/fs.ts index 9554cab..ae83b99 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -1,7 +1,9 @@ import { resolve } from 'node:path' import fs from 'node:fs' +import type { RunnerContext } from './runner' -export function getPackageJSON(cwd = process.cwd()): any { +export function getPackageJSON(ctx?: RunnerContext): any { + const cwd = ctx?.cwd ?? process.cwd() const path = resolve(cwd, 'package.json') if (fs.existsSync(path)) { @@ -11,8 +13,12 @@ export function getPackageJSON(cwd = process.cwd()): any { return data } catch (e) { - console.warn('Failed to parse package.json') - process.exit(0) + if (!ctx?.programmatic) { + console.warn('Failed to parse package.json') + process.exit(1) + } + + throw e } } } diff --git a/src/runner.ts b/src/runner.ts index 82b20b3..9933a83 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -15,6 +15,7 @@ import { UnsupportedCommand } from './parse' const DEBUG_SIGN = '?' export interface RunnerContext { + programmatic?: boolean hasLock?: boolean cwd?: string } @@ -27,10 +28,13 @@ export async function runCli(fn: Runner, options: DetectOptions = {}) { await run(fn, args, options) } catch (error) { - if (error instanceof UnsupportedCommand) + if (error instanceof UnsupportedCommand && !options.programmatic) console.log(c.red(`\u2717 ${error.message}`)) - process.exit(1) + if (!options.programmatic) + process.exit(1) + + throw error } } @@ -39,7 +43,7 @@ export async function run(fn: Runner, args: string[], options: DetectOptions = { if (debug) remove(args, DEBUG_SIGN) - let cwd = process.cwd() + let cwd = options.cwd ?? process.cwd() let command if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) { @@ -71,7 +75,7 @@ export async function run(fn: Runner, args: string[], options: DetectOptions = { command = await fn(await getGlobalAgent(), args) } else { - let agent = await detect({ ...options, cwd }) || await getDefaultAgent() + let agent = await detect({ ...options, cwd }) || await getDefaultAgent(options.programmatic) if (agent === 'prompt') { agent = (await prompts({ name: 'agent', @@ -83,6 +87,7 @@ export async function run(fn: Runner, args: string[], options: DetectOptions = { return } command = await fn(agent as Agent, args, { + programmatic: options.programmatic, hasLock: Boolean(agent), cwd, }) diff --git a/test/fixtures/lockfile/bun/bun.lockb b/test/fixtures/lockfile/bun/bun.lockb new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/lockfile/npm/package-lock.json b/test/fixtures/lockfile/npm/package-lock.json new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/lockfile/pnpm/pnpm-lock.yaml b/test/fixtures/lockfile/pnpm/pnpm-lock.yaml new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/lockfile/pnpm@6/pnpm-lock.yaml b/test/fixtures/lockfile/pnpm@6/pnpm-lock.yaml new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/lockfile/unknown/future-package-manager.json b/test/fixtures/lockfile/unknown/future-package-manager.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/fixtures/lockfile/unknown/future-package-manager.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/lockfile/yarn/yarn.lock b/test/fixtures/lockfile/yarn/yarn.lock new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/lockfile/yarn@berry/yarn.lock b/test/fixtures/lockfile/yarn@berry/yarn.lock new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/packager/bun/package.json b/test/fixtures/packager/bun/package.json new file mode 100644 index 0000000..02aefc5 --- /dev/null +++ b/test/fixtures/packager/bun/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "bun@0" +} diff --git a/test/fixtures/packager/npm/package.json b/test/fixtures/packager/npm/package.json new file mode 100644 index 0000000..dedd3ce --- /dev/null +++ b/test/fixtures/packager/npm/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "npm@7" +} diff --git a/test/fixtures/packager/pnpm/package.json b/test/fixtures/packager/pnpm/package.json new file mode 100644 index 0000000..7f6782f --- /dev/null +++ b/test/fixtures/packager/pnpm/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "pnpm@8" +} diff --git a/test/fixtures/packager/pnpm@6/package.json b/test/fixtures/packager/pnpm@6/package.json new file mode 100644 index 0000000..92c2681 --- /dev/null +++ b/test/fixtures/packager/pnpm@6/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "pnpm@6" +} diff --git a/test/fixtures/packager/unknown/package.json b/test/fixtures/packager/unknown/package.json new file mode 100644 index 0000000..a94fef9 --- /dev/null +++ b/test/fixtures/packager/unknown/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "future-package-manager" +} diff --git a/test/fixtures/packager/yarn/package.json b/test/fixtures/packager/yarn/package.json new file mode 100644 index 0000000..d40a5aa --- /dev/null +++ b/test/fixtures/packager/yarn/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "yarn@1" +} diff --git a/test/fixtures/packager/yarn@berry/package.json b/test/fixtures/packager/yarn@berry/package.json new file mode 100644 index 0000000..648f65d --- /dev/null +++ b/test/fixtures/packager/yarn@berry/package.json @@ -0,0 +1,3 @@ +{ + "packageManager": "yarn@3" +} diff --git a/test/programmatic/__snapshots__/detect.spec.ts.snap b/test/programmatic/__snapshots__/detect.spec.ts.snap new file mode 100644 index 0000000..e4038e2 --- /dev/null +++ b/test/programmatic/__snapshots__/detect.spec.ts.snap @@ -0,0 +1,29 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`lockfile > bun 1`] = `"bun"`; + +exports[`lockfile > npm 1`] = `"npm"`; + +exports[`lockfile > pnpm 1`] = `"pnpm"`; + +exports[`lockfile > pnpm@6 1`] = `"pnpm"`; + +exports[`lockfile > unknown 1`] = `null`; + +exports[`lockfile > yarn 1`] = `"yarn"`; + +exports[`lockfile > yarn@berry 1`] = `"yarn"`; + +exports[`packager > bun 1`] = `"bun"`; + +exports[`packager > npm 1`] = `"npm"`; + +exports[`packager > pnpm 1`] = `"pnpm"`; + +exports[`packager > pnpm@6 1`] = `"pnpm"`; + +exports[`packager > unknown 1`] = `null`; + +exports[`packager > yarn 1`] = `"yarn"`; + +exports[`packager > yarn@berry 1`] = `"yarn"`; diff --git a/test/programmatic/__snapshots__/runCli.spec.ts.snap b/test/programmatic/__snapshots__/runCli.spec.ts.snap new file mode 100644 index 0000000..d87cb37 --- /dev/null +++ b/test/programmatic/__snapshots__/runCli.spec.ts.snap @@ -0,0 +1,337 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`lockfile > bun > na 1`] = `"bun"`; + +exports[`lockfile > bun > na run foo 1`] = `"bun run foo"`; + +exports[`lockfile > bun > ni --frozen 1`] = `"bun install --no-save"`; + +exports[`lockfile > bun > ni -g foo 1`] = `"bun add -g foo"`; + +exports[`lockfile > bun > ni 1`] = `"bun install"`; + +exports[`lockfile > bun > ni foo -D 1`] = `"bun add foo -d"`; + +exports[`lockfile > bun > ni foo 1`] = `"bun add foo"`; + +exports[`lockfile > bun > nlx 1`] = `"bunx foo"`; + +exports[`lockfile > bun > nu -i 1`] = `"Command \\"upgrade-interactive\\" is not support by agent \\"bun\\""`; + +exports[`lockfile > bun > nu 1`] = `"Command \\"upgrade\\" is not support by agent \\"bun\\""`; + +exports[`lockfile > bun > nun -g foo 1`] = `"bun remove -g foo"`; + +exports[`lockfile > bun > nun foo 1`] = `"bun remove foo"`; + +exports[`lockfile > npm > na 1`] = `"npm"`; + +exports[`lockfile > npm > na run foo 1`] = `"npm run foo"`; + +exports[`lockfile > npm > ni --frozen 1`] = `"npm ci"`; + +exports[`lockfile > npm > ni -g foo 1`] = `"npm i -g foo"`; + +exports[`lockfile > npm > ni 1`] = `"npm i"`; + +exports[`lockfile > npm > ni foo -D 1`] = `"npm i foo -D"`; + +exports[`lockfile > npm > ni foo 1`] = `"npm i foo"`; + +exports[`lockfile > npm > nlx 1`] = `"npx foo"`; + +exports[`lockfile > npm > nu -i 1`] = `"Command \\"upgrade-interactive\\" is not support by agent \\"npm\\""`; + +exports[`lockfile > npm > nu 1`] = `"npm update"`; + +exports[`lockfile > npm > nun -g foo 1`] = `"npm uninstall -g foo"`; + +exports[`lockfile > npm > nun foo 1`] = `"npm uninstall foo"`; + +exports[`lockfile > pnpm > na 1`] = `"pnpm"`; + +exports[`lockfile > pnpm > na run foo 1`] = `"pnpm run foo"`; + +exports[`lockfile > pnpm > ni --frozen 1`] = `"pnpm i --frozen-lockfile"`; + +exports[`lockfile > pnpm > ni -g foo 1`] = `"pnpm add -g foo"`; + +exports[`lockfile > pnpm > ni 1`] = `"pnpm i"`; + +exports[`lockfile > pnpm > ni foo -D 1`] = `"pnpm add foo -D"`; + +exports[`lockfile > pnpm > ni foo 1`] = `"pnpm add foo"`; + +exports[`lockfile > pnpm > nlx 1`] = `"pnpm dlx foo"`; + +exports[`lockfile > pnpm > nu -i 1`] = `"pnpm update -i"`; + +exports[`lockfile > pnpm > nu 1`] = `"pnpm update"`; + +exports[`lockfile > pnpm > nun -g foo 1`] = `"pnpm remove --global foo"`; + +exports[`lockfile > pnpm > nun foo 1`] = `"pnpm remove foo"`; + +exports[`lockfile > pnpm@6 > na 1`] = `"pnpm"`; + +exports[`lockfile > pnpm@6 > na run foo 1`] = `"pnpm run foo"`; + +exports[`lockfile > pnpm@6 > ni --frozen 1`] = `"pnpm i --frozen-lockfile"`; + +exports[`lockfile > pnpm@6 > ni -g foo 1`] = `"pnpm add -g foo"`; + +exports[`lockfile > pnpm@6 > ni 1`] = `"pnpm i"`; + +exports[`lockfile > pnpm@6 > ni foo -D 1`] = `"pnpm add foo -D"`; + +exports[`lockfile > pnpm@6 > ni foo 1`] = `"pnpm add foo"`; + +exports[`lockfile > pnpm@6 > nlx 1`] = `"pnpm dlx foo"`; + +exports[`lockfile > pnpm@6 > nu -i 1`] = `"pnpm update -i"`; + +exports[`lockfile > pnpm@6 > nu 1`] = `"pnpm update"`; + +exports[`lockfile > pnpm@6 > nun -g foo 1`] = `"pnpm remove --global foo"`; + +exports[`lockfile > pnpm@6 > nun foo 1`] = `"pnpm remove foo"`; + +exports[`lockfile > unknown > na 1`] = `"npm"`; + +exports[`lockfile > unknown > na run foo 1`] = `"npm run foo"`; + +exports[`lockfile > unknown > ni --frozen 1`] = `"npm ci"`; + +exports[`lockfile > unknown > ni -g foo 1`] = `"npm i -g foo"`; + +exports[`lockfile > unknown > ni 1`] = `"npm i"`; + +exports[`lockfile > unknown > ni foo -D 1`] = `"npm i foo -D"`; + +exports[`lockfile > unknown > ni foo 1`] = `"npm i foo"`; + +exports[`lockfile > unknown > nlx 1`] = `"npx foo"`; + +exports[`lockfile > unknown > nu -i 1`] = `"Command \\"upgrade-interactive\\" is not support by agent \\"npm\\""`; + +exports[`lockfile > unknown > nu 1`] = `"npm update"`; + +exports[`lockfile > unknown > nun -g foo 1`] = `"npm uninstall -g foo"`; + +exports[`lockfile > unknown > nun foo 1`] = `"npm uninstall foo"`; + +exports[`lockfile > yarn > na 1`] = `"yarn"`; + +exports[`lockfile > yarn > na run foo 1`] = `"yarn run foo"`; + +exports[`lockfile > yarn > ni --frozen 1`] = `"yarn install --frozen-lockfile"`; + +exports[`lockfile > yarn > ni -g foo 1`] = `"yarn global add foo"`; + +exports[`lockfile > yarn > ni 1`] = `"yarn install"`; + +exports[`lockfile > yarn > ni foo -D 1`] = `"yarn add foo -D"`; + +exports[`lockfile > yarn > ni foo 1`] = `"yarn add foo"`; + +exports[`lockfile > yarn > nlx 1`] = `"npx foo"`; + +exports[`lockfile > yarn > nu -i 1`] = `"yarn upgrade-interactive"`; + +exports[`lockfile > yarn > nu 1`] = `"yarn upgrade"`; + +exports[`lockfile > yarn > nun -g foo 1`] = `"yarn global remove foo"`; + +exports[`lockfile > yarn > nun foo 1`] = `"yarn remove foo"`; + +exports[`lockfile > yarn@berry > na 1`] = `"yarn"`; + +exports[`lockfile > yarn@berry > na run foo 1`] = `"yarn run foo"`; + +exports[`lockfile > yarn@berry > ni --frozen 1`] = `"yarn install --frozen-lockfile"`; + +exports[`lockfile > yarn@berry > ni -g foo 1`] = `"yarn global add foo"`; + +exports[`lockfile > yarn@berry > ni 1`] = `"yarn install"`; + +exports[`lockfile > yarn@berry > ni foo -D 1`] = `"yarn add foo -D"`; + +exports[`lockfile > yarn@berry > ni foo 1`] = `"yarn add foo"`; + +exports[`lockfile > yarn@berry > nlx 1`] = `"npx foo"`; + +exports[`lockfile > yarn@berry > nu -i 1`] = `"yarn upgrade-interactive"`; + +exports[`lockfile > yarn@berry > nu 1`] = `"yarn upgrade"`; + +exports[`lockfile > yarn@berry > nun -g foo 1`] = `"yarn global remove foo"`; + +exports[`lockfile > yarn@berry > nun foo 1`] = `"yarn remove foo"`; + +exports[`packager > bun > na 1`] = `"bun"`; + +exports[`packager > bun > na run foo 1`] = `"bun run foo"`; + +exports[`packager > bun > ni --frozen 1`] = `"bun install --no-save"`; + +exports[`packager > bun > ni -g foo 1`] = `"bun add -g foo"`; + +exports[`packager > bun > ni 1`] = `"bun install"`; + +exports[`packager > bun > ni foo -D 1`] = `"bun add foo -d"`; + +exports[`packager > bun > ni foo 1`] = `"bun add foo"`; + +exports[`packager > bun > nlx 1`] = `"bunx foo"`; + +exports[`packager > bun > nu -i 1`] = `"Command \\"upgrade-interactive\\" is not support by agent \\"bun\\""`; + +exports[`packager > bun > nu 1`] = `"Command \\"upgrade\\" is not support by agent \\"bun\\""`; + +exports[`packager > bun > nun -g foo 1`] = `"bun remove -g foo"`; + +exports[`packager > bun > nun foo 1`] = `"bun remove foo"`; + +exports[`packager > npm > na 1`] = `"npm"`; + +exports[`packager > npm > na run foo 1`] = `"npm run foo"`; + +exports[`packager > npm > ni --frozen 1`] = `"npm ci"`; + +exports[`packager > npm > ni -g foo 1`] = `"npm i -g foo"`; + +exports[`packager > npm > ni 1`] = `"npm i"`; + +exports[`packager > npm > ni foo -D 1`] = `"npm i foo -D"`; + +exports[`packager > npm > ni foo 1`] = `"npm i foo"`; + +exports[`packager > npm > nlx 1`] = `"npx foo"`; + +exports[`packager > npm > nu -i 1`] = `"Command \\"upgrade-interactive\\" is not support by agent \\"npm\\""`; + +exports[`packager > npm > nu 1`] = `"npm update"`; + +exports[`packager > npm > nun -g foo 1`] = `"npm uninstall -g foo"`; + +exports[`packager > npm > nun foo 1`] = `"npm uninstall foo"`; + +exports[`packager > pnpm > na 1`] = `"pnpm"`; + +exports[`packager > pnpm > na run foo 1`] = `"pnpm run foo"`; + +exports[`packager > pnpm > ni --frozen 1`] = `"pnpm i --frozen-lockfile"`; + +exports[`packager > pnpm > ni -g foo 1`] = `"pnpm add -g foo"`; + +exports[`packager > pnpm > ni 1`] = `"pnpm i"`; + +exports[`packager > pnpm > ni foo -D 1`] = `"pnpm add foo -D"`; + +exports[`packager > pnpm > ni foo 1`] = `"pnpm add foo"`; + +exports[`packager > pnpm > nlx 1`] = `"pnpm dlx foo"`; + +exports[`packager > pnpm > nu -i 1`] = `"pnpm update -i"`; + +exports[`packager > pnpm > nu 1`] = `"pnpm update"`; + +exports[`packager > pnpm > nun -g foo 1`] = `"pnpm remove --global foo"`; + +exports[`packager > pnpm > nun foo 1`] = `"pnpm remove foo"`; + +exports[`packager > pnpm@6 > na 1`] = `"pnpm"`; + +exports[`packager > pnpm@6 > na run foo 1`] = `"pnpm run foo"`; + +exports[`packager > pnpm@6 > ni --frozen 1`] = `"pnpm i --frozen-lockfile"`; + +exports[`packager > pnpm@6 > ni -g foo 1`] = `"pnpm add -g foo"`; + +exports[`packager > pnpm@6 > ni 1`] = `"pnpm i"`; + +exports[`packager > pnpm@6 > ni foo -D 1`] = `"pnpm add foo -D"`; + +exports[`packager > pnpm@6 > ni foo 1`] = `"pnpm add foo"`; + +exports[`packager > pnpm@6 > nlx 1`] = `"pnpm dlx foo"`; + +exports[`packager > pnpm@6 > nu -i 1`] = `"pnpm update -i"`; + +exports[`packager > pnpm@6 > nu 1`] = `"pnpm update"`; + +exports[`packager > pnpm@6 > nun -g foo 1`] = `"pnpm remove --global foo"`; + +exports[`packager > pnpm@6 > nun foo 1`] = `"pnpm remove foo"`; + +exports[`packager > unknown > na 1`] = `"npm"`; + +exports[`packager > unknown > na run foo 1`] = `"npm run foo"`; + +exports[`packager > unknown > ni --frozen 1`] = `"npm ci"`; + +exports[`packager > unknown > ni -g foo 1`] = `"npm i -g foo"`; + +exports[`packager > unknown > ni 1`] = `"npm i"`; + +exports[`packager > unknown > ni foo -D 1`] = `"npm i foo -D"`; + +exports[`packager > unknown > ni foo 1`] = `"npm i foo"`; + +exports[`packager > unknown > nlx 1`] = `"npx foo"`; + +exports[`packager > unknown > nu -i 1`] = `"Command \\"upgrade-interactive\\" is not support by agent \\"npm\\""`; + +exports[`packager > unknown > nu 1`] = `"npm update"`; + +exports[`packager > unknown > nun -g foo 1`] = `"npm uninstall -g foo"`; + +exports[`packager > unknown > nun foo 1`] = `"npm uninstall foo"`; + +exports[`packager > yarn > na 1`] = `"yarn"`; + +exports[`packager > yarn > na run foo 1`] = `"yarn run foo"`; + +exports[`packager > yarn > ni --frozen 1`] = `"yarn install --frozen-lockfile"`; + +exports[`packager > yarn > ni -g foo 1`] = `"yarn global add foo"`; + +exports[`packager > yarn > ni 1`] = `"yarn install"`; + +exports[`packager > yarn > ni foo -D 1`] = `"yarn add foo -D"`; + +exports[`packager > yarn > ni foo 1`] = `"yarn add foo"`; + +exports[`packager > yarn > nlx 1`] = `"npx foo"`; + +exports[`packager > yarn > nu -i 1`] = `"yarn upgrade-interactive"`; + +exports[`packager > yarn > nu 1`] = `"yarn upgrade"`; + +exports[`packager > yarn > nun -g foo 1`] = `"yarn global remove foo"`; + +exports[`packager > yarn > nun foo 1`] = `"yarn remove foo"`; + +exports[`packager > yarn@berry > na 1`] = `"yarn"`; + +exports[`packager > yarn@berry > na run foo 1`] = `"yarn run foo"`; + +exports[`packager > yarn@berry > ni --frozen 1`] = `"yarn install --immutable"`; + +exports[`packager > yarn@berry > ni -g foo 1`] = `"npm i -g foo"`; + +exports[`packager > yarn@berry > ni 1`] = `"yarn install"`; + +exports[`packager > yarn@berry > ni foo -D 1`] = `"yarn add foo -D"`; + +exports[`packager > yarn@berry > ni foo 1`] = `"yarn add foo"`; + +exports[`packager > yarn@berry > nlx 1`] = `"yarn dlx foo"`; + +exports[`packager > yarn@berry > nu -i 1`] = `"yarn up -i"`; + +exports[`packager > yarn@berry > nu 1`] = `"yarn up"`; + +exports[`packager > yarn@berry > nun -g foo 1`] = `"npm uninstall -g foo"`; + +exports[`packager > yarn@berry > nun foo 1`] = `"yarn remove foo"`; diff --git a/test/programmatic/detect.spec.ts b/test/programmatic/detect.spec.ts new file mode 100644 index 0000000..38afd41 --- /dev/null +++ b/test/programmatic/detect.spec.ts @@ -0,0 +1,42 @@ +import path from 'node:path' +import { tmpdir } from 'node:os' +import type { SpyInstance } from 'vitest' +import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' +import fs from 'fs-extra' +import { AGENTS, detect } from '../../src' + +let basicLog: SpyInstance, errorLog: SpyInstance, warnLog: SpyInstance, infoLog: SpyInstance + +const detectTest = (agent: string) => async () => { + const cwd = await fs.mkdtemp(path.join(tmpdir(), 'ni-')) + const fixture = path.join(__dirname, '..', 'fixtures', 'lockfile', agent) + await fs.copy(fixture, cwd) + + expect(await detect({ programmatic: true, cwd })).toMatchSnapshot() +} + +beforeAll(() => { + basicLog = vi.spyOn(console, 'log') + warnLog = vi.spyOn(console, 'warn') + errorLog = vi.spyOn(console, 'error') + infoLog = vi.spyOn(console, 'info') +}) + +afterAll(() => { + vi.resetAllMocks() +}) + +const agents = [...Object.keys(AGENTS), 'unknown'] +const fixtures = ['lockfile', 'packager'] + +// matrix testing of: fixtures x agents +fixtures.forEach(fixture => describe(fixture, () => agents.forEach((agent) => { + test(agent, detectTest(agent)) + + test('no logs', () => { + expect(basicLog).not.toHaveBeenCalled() + expect(warnLog).not.toHaveBeenCalled() + expect(errorLog).not.toHaveBeenCalled() + expect(infoLog).not.toHaveBeenCalled() + }) +}))) diff --git a/test/programmatic/runCli.spec.ts b/test/programmatic/runCli.spec.ts new file mode 100644 index 0000000..e5c9fce --- /dev/null +++ b/test/programmatic/runCli.spec.ts @@ -0,0 +1,89 @@ +import path from 'node:path' +import { tmpdir } from 'node:os' +import fs from 'fs-extra' +import type { SpyInstance } from 'vitest' +import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' +import { AGENTS, parseNa, parseNi, parseNlx, parseNu, parseNun, runCli } from '../../src' + +import type { Runner } from '../../src' + +let basicLog: SpyInstance, errorLog: SpyInstance, warnLog: SpyInstance, infoLog: SpyInstance + +const runCliTest = (fixtureName: string, agent: string, runner: Runner, args: string[]) => async () => { + const cwd = await fs.mkdtemp(path.join(tmpdir(), 'ni-')) + const fixture = path.join(__dirname, '..', 'fixtures', fixtureName, agent) + await fs.copy(fixture, cwd) + + await runCli(async (agent, _, ctx) => { + // we override the args to be test specific + return runner(agent, args, ctx) + }, { + programmatic: true, + cwd, + }).catch((e) => { + // it will always throw if execa is mocked + if (e.command) + expect(e.command).toMatchSnapshot() + + else + expect(e.message).toMatchSnapshot() + }) +} + +beforeAll(() => { + basicLog = vi.spyOn(console, 'log') + warnLog = vi.spyOn(console, 'warn') + errorLog = vi.spyOn(console, 'error') + infoLog = vi.spyOn(console, 'info') + + vi.mock('execa', async (importOriginal) => { + const mod = await importOriginal() as any + return { + ...mod, + execaCommand: (cmd: string) => { + // break execution flow for easier snapshotting + // eslint-disable-next-line no-throw-literal + throw { command: cmd } + }, + } + }) +}) + +afterAll(() => { + vi.resetAllMocks() +}) + +const agents = [...Object.keys(AGENTS), 'unknown'] +const fixtures = ['lockfile', 'packager'] + +// matrix testing of: fixtures x agents x commands +fixtures.forEach(fixture => describe(fixture, () => agents.forEach(agent => describe(agent, () => { + /** na */ + test('na', runCliTest(fixture, agent, parseNa, [])) + test('na run foo', runCliTest(fixture, agent, parseNa, ['run', 'foo'])) + + /** ni */ + test('ni', runCliTest(fixture, agent, parseNi, [])) + test('ni foo', runCliTest(fixture, agent, parseNi, ['foo'])) + test('ni foo -D', runCliTest(fixture, agent, parseNi, ['foo', '-D'])) + test('ni --frozen', runCliTest(fixture, agent, parseNi, ['--frozen'])) + test('ni -g foo', runCliTest(fixture, agent, parseNi, ['-g', 'foo'])) + + /** nlx */ + test('nlx', runCliTest(fixture, agent, parseNlx, ['foo'])) + + /** nu */ + test('nu', runCliTest(fixture, agent, parseNu, [])) + test('nu -i', runCliTest(fixture, agent, parseNu, ['-i'])) + + /** nun */ + test('nun foo', runCliTest(fixture, agent, parseNun, ['foo'])) + test('nun -g foo', runCliTest(fixture, agent, parseNun, ['-g', 'foo'])) + + test('no logs', () => { + expect(basicLog).not.toHaveBeenCalled() + expect(warnLog).not.toHaveBeenCalled() + expect(errorLog).not.toHaveBeenCalled() + expect(infoLog).not.toHaveBeenCalled() + }) +}))))