@ankhorage/devtools 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@ Shared ESLint, Prettier, and Knip configuration for modern TypeScript projects.
8
8
  - Zero-config Prettier setup
9
9
  - Shared Knip static-analysis defaults
10
10
  - Bundled tool binaries for ESLint, Prettier, and Knip
11
+ - Ankh provider commands for lint, format, and Knip
11
12
  - Strict TypeScript rules without compromise
12
13
  - One source of truth for tooling
13
14
 
@@ -16,8 +17,9 @@ Shared ESLint, Prettier, and Knip configuration for modern TypeScript projects.
16
17
  - Flat ESLint config (latest standard)
17
18
  - Preconfigured plugin ecosystem
18
19
  - Prettier integration
19
- - Shared Knip config factory
20
+ - Shared Knip config factories
20
21
  - `ankhorage-eslint`, `ankhorage-prettier`, and `ankhorage-knip` binaries
22
+ - `ankh devtools lint`, `ankh devtools format`, and `ankh devtools knip`
21
23
  - Monorepo-friendly
22
24
 
23
25
  ## Installation
@@ -28,6 +30,16 @@ bun add -D @ankhorage/devtools
28
30
 
29
31
  The package owns the ESLint, Prettier, and Knip toolchain. Consuming repos should not install `eslint`, `prettier`, or `knip` directly unless they intentionally need a different version from the shared Ankhorage toolchain.
30
32
 
33
+ It also participates in Ankh package discovery with:
34
+
35
+ - category: `devtools`
36
+ - capabilities:
37
+ - `devtools.lint`
38
+ - `devtools.format`
39
+ - `devtools.knip`
40
+
41
+ `@ankhorage/devtools` owns primitive lint/format/knip tooling. Local emulator, app, and workstation workflows belong in `@ankhorage/dev`.
42
+
31
43
  ## Usage
32
44
 
33
45
  ### Scripts
@@ -46,6 +58,22 @@ Use the devtools-owned binaries in package scripts:
46
58
  }
47
59
  ```
48
60
 
61
+ ### Ankh commands
62
+
63
+ When discovered by `@ankhorage/ankh`, the package exposes:
64
+
65
+ ```bash
66
+ ankh devtools lint -- --max-warnings=0 .
67
+ ankh devtools format -- --check .
68
+ ankh devtools knip -- --production
69
+ ```
70
+
71
+ These provider-backed commands delegate to the same underlying tools as the standalone binaries:
72
+
73
+ - `ankh devtools lint` -> `ankhorage-eslint`
74
+ - `ankh devtools format` -> `ankhorage-prettier`
75
+ - `ankh devtools knip` -> `ankhorage-knip`
76
+
49
77
  ### ESLint
50
78
 
51
79
  Create an `eslint.config.js` file:
@@ -98,7 +126,41 @@ export default createKnipConfig({
98
126
  });
99
127
  ```
100
128
 
101
- The shared config intentionally keeps defaults narrow so Knip can use its own zero-config package discovery. Prefer explicit `entry`, `project`, `ignoreBinaries`, `ignoreDependencies`, or `ignoreFiles` over broad ignores.
129
+ For workspaces-based monorepos, use the monorepo preset:
130
+
131
+ ```ts
132
+ import { createKnipMonorepoConfig } from '@ankhorage/devtools/knip';
133
+
134
+ export default createKnipMonorepoConfig({
135
+ root: {
136
+ ignoreFiles: ['.prettierrc.js', 'eslint.config.js'],
137
+ },
138
+ });
139
+ ```
140
+
141
+ By default, the monorepo preset configures Knip workspaces for the root package, `packages/*`, and `apps/*`. Repos can override those defaults or add extra workspace globs:
142
+
143
+ ```ts
144
+ import { createKnipMonorepoConfig } from '@ankhorage/devtools/knip';
145
+
146
+ export default createKnipMonorepoConfig({
147
+ workspaceGlobs: ['packages/*', 'apps/*', 'examples/*'],
148
+ workspaceDefaults: {
149
+ ignoreFiles: ['fixtures/**'],
150
+ },
151
+ workspaces: {
152
+ '.': {
153
+ ignoreFiles: ['.prettierrc.js', 'eslint.config.js'],
154
+ },
155
+ 'apps/editor': {
156
+ ignoreFiles: ['babel.config.js'],
157
+ ignoreDependencies: ['babel-preset-expo'],
158
+ },
159
+ },
160
+ });
161
+ ```
162
+
163
+ The shared config intentionally keeps defaults narrow so Knip can still report real unused files, exports, dependencies, and binaries. Prefer explicit `entry`, `project`, `ignoreBinaries`, `ignoreDependencies`, or `ignoreFiles` over broad ignores.
102
164
 
103
165
  ### CI
104
166
 
@@ -151,8 +213,10 @@ Includes:
151
213
  - ESLint configuration and binary wrapper
152
214
  - Prettier configuration and binary wrapper
153
215
  - Knip configuration and binary wrapper
216
+ - Ankh provider descriptors and handlers for lint, format, and Knip
154
217
 
155
218
  Excludes:
156
219
 
157
220
  - runtime code
158
221
  - build tooling
222
+ - local dev workflows
@@ -0,0 +1,18 @@
1
+ declare const provider: {
2
+ id: string;
3
+ category: string;
4
+ version: string;
5
+ capabilities: ("devtools.format" | "devtools.knip" | "devtools.lint")[];
6
+ commands: {
7
+ path: [import("./internal/devtoolsCommands.js").DevtoolsToolName];
8
+ capability: "devtools.format" | "devtools.knip" | "devtools.lint";
9
+ summary: string;
10
+ }[];
11
+ handlers: {
12
+ path: [import("./internal/devtoolsCommands.js").DevtoolsToolName];
13
+ handler: (request: import("@ankhorage/ankh").AnkhCommandExecutionRequest) => Promise<{
14
+ exitCode: number;
15
+ }>;
16
+ }[];
17
+ };
18
+ export default provider;
@@ -0,0 +1,37 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { getDevtoolsCommands } from './internal/devtoolsCommands.js';
3
+ import { runDevtoolsCommand } from './internal/runDevtoolsCommand.js';
4
+ const packageVersion = readPackageVersion();
5
+ const commands = getDevtoolsCommands();
6
+ const provider = {
7
+ id: '@ankhorage/devtools',
8
+ category: 'devtools',
9
+ version: packageVersion,
10
+ capabilities: commands.map((command) => command.capability),
11
+ commands: commands.map((command) => ({
12
+ path: [...command.path],
13
+ capability: command.capability,
14
+ summary: command.summary,
15
+ })),
16
+ handlers: commands.map((command) => ({
17
+ path: [...command.path],
18
+ handler: async (request) => {
19
+ const result = await runDevtoolsCommand(command, request.argv);
20
+ return { exitCode: result.exitCode };
21
+ },
22
+ })),
23
+ };
24
+ export default provider;
25
+ function readPackageVersion() {
26
+ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
27
+ if (!isRecord(packageJson) || !isNonEmptyString(packageJson.version)) {
28
+ throw new Error('package.json must define a non-empty version string.');
29
+ }
30
+ return packageJson.version;
31
+ }
32
+ function isNonEmptyString(value) {
33
+ return typeof value === 'string' && value.trim() !== '';
34
+ }
35
+ function isRecord(value) {
36
+ return typeof value === 'object' && value !== null;
37
+ }
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { runPackageBin } from './internal/runPackageBin.js';
3
- runPackageBin('eslint', 'eslint');
2
+ import { runStandaloneDevtoolsCommand } from './internal/runStandaloneDevtoolsCommand.js';
3
+ const result = await runStandaloneDevtoolsCommand('lint', process.argv.slice(2));
4
+ process.exit(result.exitCode);
@@ -0,0 +1,11 @@
1
+ export type DevtoolsToolName = 'format' | 'knip' | 'lint';
2
+ export interface DevtoolsCommandDefinition {
3
+ path: readonly [DevtoolsToolName];
4
+ capability: 'devtools.format' | 'devtools.knip' | 'devtools.lint';
5
+ summary: string;
6
+ packageName: 'eslint' | 'knip' | 'prettier';
7
+ binName: 'eslint' | 'knip' | 'prettier';
8
+ }
9
+ export declare function getDevtoolsCommands(): readonly DevtoolsCommandDefinition[];
10
+ export declare function findDevtoolsCommandByPath(path: readonly string[]): DevtoolsCommandDefinition | null;
11
+ export declare function getDevtoolsCommand(toolName: DevtoolsToolName): DevtoolsCommandDefinition;
@@ -0,0 +1,39 @@
1
+ const DEVTOOLS_COMMANDS = [
2
+ {
3
+ path: ['lint'],
4
+ capability: 'devtools.lint',
5
+ summary: 'Run the shared ESLint toolchain.',
6
+ packageName: 'eslint',
7
+ binName: 'eslint',
8
+ },
9
+ {
10
+ path: ['format'],
11
+ capability: 'devtools.format',
12
+ summary: 'Run the shared Prettier toolchain.',
13
+ packageName: 'prettier',
14
+ binName: 'prettier',
15
+ },
16
+ {
17
+ path: ['knip'],
18
+ capability: 'devtools.knip',
19
+ summary: 'Run the shared Knip toolchain.',
20
+ packageName: 'knip',
21
+ binName: 'knip',
22
+ },
23
+ ];
24
+ export function getDevtoolsCommands() {
25
+ return DEVTOOLS_COMMANDS;
26
+ }
27
+ export function findDevtoolsCommandByPath(path) {
28
+ if (path.length !== 1) {
29
+ return null;
30
+ }
31
+ return DEVTOOLS_COMMANDS.find((command) => command.path[0] === path[0]) ?? null;
32
+ }
33
+ export function getDevtoolsCommand(toolName) {
34
+ const command = DEVTOOLS_COMMANDS.find((candidate) => candidate.path[0] === toolName);
35
+ if (command === undefined) {
36
+ throw new Error(`Unknown devtools command: ${toolName}`);
37
+ }
38
+ return command;
39
+ }
@@ -0,0 +1 @@
1
+ export declare function getReadmeDocumentationErrors(readmeContents: string): string[];
@@ -0,0 +1,17 @@
1
+ const REQUIRED_README_SNIPPETS = [
2
+ 'ankh devtools lint',
3
+ 'ankh devtools format',
4
+ 'ankh devtools knip',
5
+ 'ankhorage-eslint',
6
+ 'ankhorage-prettier',
7
+ 'ankhorage-knip',
8
+ 'devtools',
9
+ 'devtools.lint',
10
+ 'devtools.format',
11
+ 'devtools.knip',
12
+ ];
13
+ export function getReadmeDocumentationErrors(readmeContents) {
14
+ return REQUIRED_README_SNIPPETS.flatMap((snippet) => readmeContents.includes(snippet)
15
+ ? []
16
+ : [`README.md is missing required documentation snippet: ${snippet}`]);
17
+ }
@@ -0,0 +1,31 @@
1
+ import type { DevtoolsCommandDefinition, DevtoolsToolName } from './devtoolsCommands.js';
2
+ export type { DevtoolsCommandDefinition, DevtoolsToolName };
3
+ export interface DevtoolsRunResult {
4
+ exitCode: number;
5
+ }
6
+ interface DevtoolsRunnerOptions {
7
+ cwd?: string;
8
+ env?: NodeJS.ProcessEnv;
9
+ }
10
+ interface ResolvedExecutionTarget {
11
+ command: string;
12
+ args: readonly string[];
13
+ shell: boolean;
14
+ }
15
+ interface SpawnedProcess {
16
+ on(event: 'error', listener: (error: Error) => void): SpawnedProcess;
17
+ on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): SpawnedProcess;
18
+ }
19
+ type SpawnProcess = (command: string, args: readonly string[], options: {
20
+ cwd: string;
21
+ env: NodeJS.ProcessEnv;
22
+ shell: boolean;
23
+ stdio: 'inherit';
24
+ }) => SpawnedProcess;
25
+ interface DevtoolsRunnerDependencies {
26
+ readonly logError: (message: string) => void;
27
+ readonly resolveExecutionTarget: (command: DevtoolsCommandDefinition) => Promise<ResolvedExecutionTarget>;
28
+ readonly spawnProcess: SpawnProcess;
29
+ }
30
+ export declare function runDevtoolsCommand(command: DevtoolsCommandDefinition, argv: readonly string[], options?: DevtoolsRunnerOptions): Promise<DevtoolsRunResult>;
31
+ export declare function runDevtoolsCommandWithDependencies(command: DevtoolsCommandDefinition, argv: readonly string[], options: DevtoolsRunnerOptions | undefined, dependencies: DevtoolsRunnerDependencies): Promise<DevtoolsRunResult>;
@@ -0,0 +1,118 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { createRequire } from 'node:module';
5
+ import { dirname, extname, join, resolve } from 'node:path';
6
+ const require = createRequire(import.meta.url);
7
+ const defaultRunnerDependencies = {
8
+ logError: (message) => {
9
+ console.error(message);
10
+ },
11
+ resolveExecutionTarget,
12
+ spawnProcess: (command, args, options) => spawn(command, args, options),
13
+ };
14
+ export async function runDevtoolsCommand(command, argv, options) {
15
+ return runDevtoolsCommandWithDependencies(command, argv, options, defaultRunnerDependencies);
16
+ }
17
+ export async function runDevtoolsCommandWithDependencies(command, argv, options, dependencies) {
18
+ let executionTarget;
19
+ try {
20
+ executionTarget = await dependencies.resolveExecutionTarget(command);
21
+ }
22
+ catch (error) {
23
+ dependencies.logError(`Failed to resolve ${command.binName}: ${getErrorMessage(error)}`);
24
+ return { exitCode: 1 };
25
+ }
26
+ const cwd = options?.cwd ?? process.cwd();
27
+ const env = options?.env ?? process.env;
28
+ return await new Promise((resolveResult) => {
29
+ let settled = false;
30
+ const settle = (result) => {
31
+ if (settled) {
32
+ return;
33
+ }
34
+ settled = true;
35
+ resolveResult(result);
36
+ };
37
+ const child = dependencies.spawnProcess(executionTarget.command, [...executionTarget.args, ...argv], {
38
+ cwd,
39
+ env,
40
+ shell: executionTarget.shell,
41
+ stdio: 'inherit',
42
+ });
43
+ child.on('error', (error) => {
44
+ dependencies.logError(`Failed to start ${command.binName}: ${getErrorMessage(error)}`);
45
+ settle({ exitCode: 1 });
46
+ });
47
+ child.on('exit', (code, signal) => {
48
+ if (signal !== null) {
49
+ dependencies.logError(`${command.binName} exited with signal ${signal}.`);
50
+ settle({ exitCode: 1 });
51
+ return;
52
+ }
53
+ settle({ exitCode: code ?? 1 });
54
+ });
55
+ });
56
+ }
57
+ async function resolveExecutionTarget(command) {
58
+ const binPath = await readPackageBinPath(command.packageName, command.binName);
59
+ if (await shouldExecuteWithNode(binPath)) {
60
+ return {
61
+ command: process.execPath,
62
+ args: [binPath],
63
+ shell: false,
64
+ };
65
+ }
66
+ return {
67
+ command: binPath,
68
+ args: [],
69
+ shell: process.platform === 'win32',
70
+ };
71
+ }
72
+ function findPackageJsonPath(packageName) {
73
+ let currentDirectory = dirname(require.resolve(packageName));
74
+ while (currentDirectory !== dirname(currentDirectory)) {
75
+ const packageJsonPath = join(currentDirectory, 'package.json');
76
+ if (existsSync(packageJsonPath)) {
77
+ return packageJsonPath;
78
+ }
79
+ currentDirectory = dirname(currentDirectory);
80
+ }
81
+ throw new Error(`Could not find package metadata for ${packageName}.`);
82
+ }
83
+ async function readPackageBinPath(packageName, binName) {
84
+ const packageJsonPath = findPackageJsonPath(packageName);
85
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
86
+ if (!isRecord(packageJson)) {
87
+ throw new Error(`Package metadata for ${packageName} is not an object.`);
88
+ }
89
+ const { bin } = packageJson;
90
+ let relativeBinPath;
91
+ if (typeof bin === 'string') {
92
+ relativeBinPath = bin;
93
+ }
94
+ else if (isRecord(bin)) {
95
+ const namedBin = bin[binName];
96
+ if (typeof namedBin === 'string') {
97
+ relativeBinPath = namedBin;
98
+ }
99
+ }
100
+ if (relativeBinPath === undefined) {
101
+ throw new Error(`Package ${packageName} does not expose a ${binName} binary.`);
102
+ }
103
+ return resolve(dirname(packageJsonPath), relativeBinPath);
104
+ }
105
+ async function shouldExecuteWithNode(binPath) {
106
+ const extension = extname(binPath).toLowerCase();
107
+ if (extension === '.cjs' || extension === '.js' || extension === '.mjs') {
108
+ return true;
109
+ }
110
+ const firstLine = (await readFile(binPath, 'utf8')).split('\n', 1)[0] ?? '';
111
+ return firstLine.startsWith('#!') && firstLine.includes('node');
112
+ }
113
+ function getErrorMessage(error) {
114
+ return error instanceof Error ? error.message : String(error);
115
+ }
116
+ function isRecord(value) {
117
+ return typeof value === 'object' && value !== null;
118
+ }
@@ -0,0 +1,3 @@
1
+ import type { DevtoolsToolName } from './runDevtoolsCommand.js';
2
+ import { type DevtoolsRunResult } from './runDevtoolsCommand.js';
3
+ export declare function runStandaloneDevtoolsCommand(toolName: DevtoolsToolName, argv: readonly string[]): Promise<DevtoolsRunResult>;
@@ -0,0 +1,5 @@
1
+ import { getDevtoolsCommand } from './devtoolsCommands.js';
2
+ import { runDevtoolsCommand } from './runDevtoolsCommand.js';
3
+ export async function runStandaloneDevtoolsCommand(toolName, argv) {
4
+ return runDevtoolsCommand(getDevtoolsCommand(toolName), argv);
5
+ }
package/dist/knip-cli.js CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { runPackageBin } from './internal/runPackageBin.js';
3
- runPackageBin('knip', 'knip');
2
+ import { runStandaloneDevtoolsCommand } from './internal/runStandaloneDevtoolsCommand.js';
3
+ const result = await runStandaloneDevtoolsCommand('knip', process.argv.slice(2));
4
+ process.exit(result.exitCode);
package/dist/knip.d.ts CHANGED
@@ -10,4 +10,11 @@ export interface DevtoolsKnipWorkspaceConfigOptions {
10
10
  export interface DevtoolsKnipConfigOptions extends DevtoolsKnipWorkspaceConfigOptions {
11
11
  workspaces?: Record<string, DevtoolsKnipWorkspaceConfigOptions>;
12
12
  }
13
+ export interface DevtoolsKnipMonorepoConfigOptions {
14
+ root?: DevtoolsKnipWorkspaceConfigOptions;
15
+ workspaceDefaults?: DevtoolsKnipWorkspaceConfigOptions;
16
+ workspaceGlobs?: string[];
17
+ workspaces?: Record<string, DevtoolsKnipWorkspaceConfigOptions>;
18
+ }
13
19
  export declare function createKnipConfig(options?: DevtoolsKnipConfigOptions): KnipConfig;
20
+ export declare function createKnipMonorepoConfig(options?: DevtoolsKnipMonorepoConfigOptions): KnipConfig;
package/dist/knip.js CHANGED
@@ -1,3 +1,4 @@
1
+ const DEFAULT_MONOREPO_WORKSPACE_GLOBS = ['packages/*', 'apps/*'];
1
2
  export function createKnipConfig(options = {}) {
2
3
  return {
3
4
  ...(options.entry === undefined ? {} : { entry: options.entry }),
@@ -11,3 +12,20 @@ export function createKnipConfig(options = {}) {
11
12
  ...(options.workspaces === undefined ? {} : { workspaces: options.workspaces }),
12
13
  };
13
14
  }
15
+ export function createKnipMonorepoConfig(options = {}) {
16
+ const explicitWorkspaces = options.workspaces ?? {};
17
+ const workspaceGlobs = options.workspaceGlobs ?? [...DEFAULT_MONOREPO_WORKSPACE_GLOBS];
18
+ const workspaces = {
19
+ '.': options.root ?? {},
20
+ };
21
+ for (const workspaceGlob of workspaceGlobs) {
22
+ workspaces[workspaceGlob] = {
23
+ ...(options.workspaceDefaults ?? {}),
24
+ ...(explicitWorkspaces[workspaceGlob] ?? {}),
25
+ };
26
+ }
27
+ for (const [workspaceGlob, workspaceConfig] of Object.entries(explicitWorkspaces)) {
28
+ workspaces[workspaceGlob] = workspaceConfig;
29
+ }
30
+ return createKnipConfig({ workspaces });
31
+ }
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { runPackageBin } from './internal/runPackageBin.js';
3
- runPackageBin('prettier', 'prettier');
2
+ import { runStandaloneDevtoolsCommand } from './internal/runStandaloneDevtoolsCommand.js';
3
+ const result = await runStandaloneDevtoolsCommand('format', process.argv.slice(2));
4
+ process.exit(result.exitCode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Shared Lint and Format Configuration for Ankhorage",
5
5
  "homepage": "https://github.com/ankhorage/devtools#readme",
6
6
  "bugs": {
@@ -14,6 +14,15 @@
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
+ "ankh": {
18
+ "category": "devtools",
19
+ "provider": "./dist/ankh.provider.js",
20
+ "capabilities": [
21
+ "devtools.lint",
22
+ "devtools.format",
23
+ "devtools.knip"
24
+ ]
25
+ },
17
26
  "keywords": [
18
27
  "typescript",
19
28
  "eslint",
@@ -49,13 +58,18 @@
49
58
  "examples"
50
59
  ],
51
60
  "scripts": {
52
- "build": "tsc && cp src/prettier.cjs dist/prettier.cjs",
61
+ "build": "rm -rf dist tsconfig.tsbuildinfo && tsc && cp src/prettier.cjs dist/prettier.cjs",
62
+ "typecheck": "bun x tsc --noEmit -p tsconfig.test.json",
53
63
  "knip": "knip",
54
64
  "lint": "eslint .",
55
65
  "lint:fix": "eslint . --fix --max-warnings=0",
56
66
  "format": "prettier --write .",
57
67
  "format:check": "prettier --check .",
68
+ "docs": "bun run docs:check",
69
+ "docs:check": "bun scripts/check-docs.ts",
58
70
  "test": "bun test",
71
+ "changeset": "changeset",
72
+ "changeset:status": "changeset status --since=origin/main",
59
73
  "version-packages": "changeset version"
60
74
  },
61
75
  "dependencies": {
@@ -71,6 +85,7 @@
71
85
  "typescript-eslint": "^8.24.0"
72
86
  },
73
87
  "devDependencies": {
88
+ "@ankhorage/ankh": "^0.4.0",
74
89
  "@changesets/cli": "^2.30.0",
75
90
  "@types/bun": "^1.3.13",
76
91
  "@types/node": "^25.2.3",
@@ -1 +0,0 @@
1
- export declare function runPackageBin(packageName: string, binName: string): void;
@@ -1,58 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { createRequire } from 'node:module';
4
- import { dirname, join, resolve } from 'node:path';
5
- const require = createRequire(import.meta.url);
6
- function isRecord(value) {
7
- return typeof value === 'object' && value !== null;
8
- }
9
- function findPackageJsonPath(packageName) {
10
- let currentDirectory = dirname(require.resolve(packageName));
11
- while (currentDirectory !== dirname(currentDirectory)) {
12
- const packageJsonPath = join(currentDirectory, 'package.json');
13
- if (existsSync(packageJsonPath)) {
14
- return packageJsonPath;
15
- }
16
- currentDirectory = dirname(currentDirectory);
17
- }
18
- throw new Error(`Could not find package metadata for ${packageName}.`);
19
- }
20
- function readPackageBinPath(packageName, binName) {
21
- const packageJsonPath = findPackageJsonPath(packageName);
22
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
23
- if (!isRecord(packageJson)) {
24
- throw new Error(`Package metadata for ${packageName} is not an object.`);
25
- }
26
- const { bin } = packageJson;
27
- let relativeBinPath;
28
- if (typeof bin === 'string') {
29
- relativeBinPath = bin;
30
- }
31
- else if (isRecord(bin)) {
32
- const namedBin = bin[binName];
33
- if (typeof namedBin === 'string') {
34
- relativeBinPath = namedBin;
35
- }
36
- }
37
- if (relativeBinPath === undefined) {
38
- throw new Error(`Package ${packageName} does not expose a ${binName} binary.`);
39
- }
40
- return resolve(dirname(packageJsonPath), relativeBinPath);
41
- }
42
- export function runPackageBin(packageName, binName) {
43
- const binPath = readPackageBinPath(packageName, binName);
44
- const child = spawn(process.execPath, [binPath, ...process.argv.slice(2)], {
45
- stdio: 'inherit',
46
- });
47
- child.on('error', (error) => {
48
- console.error(error);
49
- process.exit(1);
50
- });
51
- child.on('exit', (code, signal) => {
52
- if (signal !== null) {
53
- console.error(`${binName} exited with signal ${signal}.`);
54
- process.exit(1);
55
- }
56
- process.exit(code ?? 1);
57
- });
58
- }