@ankhorage/devtools 1.0.6 → 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 +30 -0
- package/dist/ankh.provider.d.ts +18 -0
- package/dist/ankh.provider.js +37 -0
- package/dist/eslint-cli.js +3 -2
- package/dist/internal/devtoolsCommands.d.ts +11 -0
- package/dist/internal/devtoolsCommands.js +39 -0
- package/dist/internal/readmeDocs.d.ts +1 -0
- package/dist/internal/readmeDocs.js +17 -0
- package/dist/internal/runDevtoolsCommand.d.ts +31 -0
- package/dist/internal/runDevtoolsCommand.js +118 -0
- package/dist/internal/runStandaloneDevtoolsCommand.d.ts +3 -0
- package/dist/internal/runStandaloneDevtoolsCommand.js +5 -0
- package/dist/knip-cli.js +3 -2
- package/dist/prettier-cli.js +3 -2
- package/package.json +17 -2
- package/dist/internal/runPackageBin.d.ts +0 -1
- package/dist/internal/runPackageBin.js +0 -58
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
|
|
|
@@ -18,6 +19,7 @@ Shared ESLint, Prettier, and Knip configuration for modern TypeScript projects.
|
|
|
18
19
|
- Prettier integration
|
|
19
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:
|
|
@@ -185,8 +213,10 @@ Includes:
|
|
|
185
213
|
- ESLint configuration and binary wrapper
|
|
186
214
|
- Prettier configuration and binary wrapper
|
|
187
215
|
- Knip configuration and binary wrapper
|
|
216
|
+
- Ankh provider descriptors and handlers for lint, format, and Knip
|
|
188
217
|
|
|
189
218
|
Excludes:
|
|
190
219
|
|
|
191
220
|
- runtime code
|
|
192
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
|
+
}
|
package/dist/eslint-cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
+
}
|
package/dist/knip-cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { runStandaloneDevtoolsCommand } from './internal/runStandaloneDevtoolsCommand.js';
|
|
3
|
+
const result = await runStandaloneDevtoolsCommand('knip', process.argv.slice(2));
|
|
4
|
+
process.exit(result.exitCode);
|
package/dist/prettier-cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
|
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
|
-
}
|