@slats/claude-assets-sync 0.2.0 → 0.3.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 +46 -62
- package/bin/inject-claude-settings.mjs +4 -0
- package/dist/claude-hashes.json +9 -9
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/runCli/index.d.ts +1 -1
- package/dist/commands/runCli/runCli.cjs +27 -5
- package/dist/commands/runCli/runCli.d.ts +10 -6
- package/dist/commands/runCli/runCli.mjs +27 -5
- package/dist/commands/runCli/type.d.ts +3 -12
- package/dist/commands/runCli/utils/classifyTarget.cjs +48 -0
- package/dist/commands/runCli/utils/classifyTarget.d.ts +19 -0
- package/dist/commands/runCli/utils/classifyTarget.mjs +46 -0
- package/dist/commands/runCli/utils/injectOne.cjs +2 -3
- package/dist/commands/runCli/utils/injectOne.d.ts +1 -1
- package/dist/commands/runCli/utils/injectOne.mjs +2 -3
- package/dist/commands/runCli/utils/resolvePackage.cjs +77 -0
- package/dist/commands/runCli/utils/resolvePackage.d.ts +16 -0
- package/dist/commands/runCli/utils/resolvePackage.mjs +74 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.cjs +69 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.d.ts +2 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.mjs +67 -0
- package/dist/commands/runCli/utils/resolveTargets.cjs +40 -0
- package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
- package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
- package/dist/commands/runCli/utils/runInject.cjs +38 -22
- package/dist/commands/runCli/utils/runInject.d.ts +3 -2
- package/dist/commands/runCli/utils/runInject.mjs +38 -22
- package/dist/core/injectDocs/utils/applyAction.cjs +1 -1
- package/dist/core/injectDocs/utils/applyAction.mjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/utils/version.cjs +1 -1
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.mjs +1 -1
- package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +86 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +122 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +145 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
- package/docs/consumer-integration.md +41 -100
- package/package.json +2 -2
- package/bin/claude-sync.mjs +0 -24
- package/docs/claude/skills/claude-sync-applier/SKILL.md +0 -195
- package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +0 -77
- package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +0 -126
- package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +0 -139
- package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +0 -130
- package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +0 -120
- package/docs/claude/skills/claude-sync-applier/knowledge/smoke-tests.md +0 -102
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# @slats/claude-assets-sync
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Engine + dispatcher CLI that lets any npm package ship its own Claude Code docs (skills, rules, commands) and inject them into a user's `.claude/` directory. Consumers declare `claude.assetPath` in `package.json` and the engine's `inject-claude-settings` bin handles the rest.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
A consumer package
|
|
7
|
+
A consumer package declares `claude.assetPath` in `package.json` and runs `claude-build-hashes` during build to emit `dist/claude-hashes.json`. End users run `npx -p @slats/claude-assets-sync inject-claude-settings --package=<name>` and this engine resolves that single package's metadata via `createRequire`, compares the hash manifest against the target `.claude/`, and copies only what is out of date.
|
|
8
|
+
|
|
9
|
+
The library operates on exactly one consumer per invocation — the one named in `--package`. It never walks `node_modules` for siblings; it never enumerates workspaces.
|
|
8
10
|
|
|
9
11
|
No GitHub fetch, no `.sync-meta.json`, no migrations — the consumer's `dist/claude-hashes.json` is the single source of truth.
|
|
10
12
|
|
|
@@ -19,96 +21,72 @@ yarn add -D @slats/claude-assets-sync
|
|
|
19
21
|
## CLI Surface
|
|
20
22
|
|
|
21
23
|
```
|
|
22
|
-
claude-
|
|
24
|
+
inject-claude-settings --package=<name> [--scope=user|project] [--dry-run] [--force] [--root=<cwd>]
|
|
25
|
+
claude-build-hashes
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
### End-user invocation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# universal — every PM (pnpm strict / yarn-berry PnP included)
|
|
32
|
+
npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=user
|
|
33
|
+
|
|
34
|
+
# simple — npm / yarn-classic only (relies on transitive bin hoist from the consumer's dependencies)
|
|
35
|
+
npx inject-claude-settings --package=@canard/schema-form --scope=user
|
|
36
|
+
```
|
|
26
37
|
|
|
27
38
|
| Flag | Meaning |
|
|
28
39
|
|---|---|
|
|
29
|
-
| `--
|
|
30
|
-
| `--scope=
|
|
31
|
-
| `--
|
|
32
|
-
| `--
|
|
40
|
+
| `--package <name>` | **Required.** Scoped npm name of a consumer that declares `claude.assetPath`. |
|
|
41
|
+
| `--scope=user` | `~/.claude` (applies globally). |
|
|
42
|
+
| `--scope=project` | Nearest ancestor `.claude` directory, or `<cwd>/.claude` if none found. |
|
|
43
|
+
| `--dry-run` | Print the copy / skip / warn plan, no writes. |
|
|
44
|
+
| `--force` | Overwrite diverged files & delete orphans (interactive confirm on TTY). |
|
|
45
|
+
| `--root <path>` | Override scope-resolution cwd. |
|
|
33
46
|
|
|
34
|
-
**Exit codes**: `0` success / up-to-date / dry-run, `1` runtime error, `2` user / configuration error (
|
|
47
|
+
**Exit codes**: `0` success / up-to-date / dry-run, `1` runtime error, `2` user / configuration error (missing `--package`, missing `--scope` in non-TTY, unresolvable package, missing `claude.assetPath`).
|
|
35
48
|
|
|
36
49
|
For `--scope=project` the target `.claude` directory is resolved by walking up from `process.cwd()` to the nearest existing `.claude` ancestor; the CLI logs `(auto-located)` when this happens.
|
|
37
50
|
|
|
38
|
-
## Consumer Integration (
|
|
51
|
+
## Consumer Integration (2 steps)
|
|
39
52
|
|
|
40
53
|
### 1. `package.json`
|
|
41
54
|
|
|
42
55
|
```jsonc
|
|
43
56
|
{
|
|
44
57
|
"name": "@your-scope/your-package",
|
|
45
|
-
"bin": { "claude-sync": "./bin/claude-sync.mjs" },
|
|
46
|
-
"files": ["dist", "docs", "dist/claude-hashes.json", "bin", "README.md"],
|
|
47
58
|
"scripts": {
|
|
48
59
|
"build": "… && yarn build:hashes",
|
|
49
|
-
"build:hashes": "
|
|
60
|
+
"build:hashes": "claude-build-hashes"
|
|
50
61
|
},
|
|
51
62
|
"dependencies": {
|
|
52
63
|
"@slats/claude-assets-sync": "workspace:^"
|
|
53
64
|
},
|
|
65
|
+
"files": ["dist", "docs", "README.md"],
|
|
54
66
|
"claude": { "assetPath": "docs/claude" }
|
|
55
67
|
}
|
|
56
68
|
```
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
#!/usr/bin/env node
|
|
64
|
-
import { runCli } from '@slats/claude-assets-sync';
|
|
65
|
-
import { readFile } from 'node:fs/promises';
|
|
66
|
-
import { dirname, resolve } from 'node:path';
|
|
67
|
-
import { fileURLToPath } from 'node:url';
|
|
68
|
-
|
|
69
|
-
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
70
|
-
const pkg = JSON.parse(
|
|
71
|
-
await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
|
|
72
|
-
);
|
|
70
|
+
- `@slats/claude-assets-sync` MUST be in `dependencies`, not `devDependencies` — see Rationale below.
|
|
71
|
+
- Do **not** add any `bin` field. The engine is the sole CLI surface; per-consumer bins would collide under `node_modules/.bin/`.
|
|
72
|
+
- Do **not** expose `./bin/*` or `./docs/*` in `exports`. That would let consumer bundlers pull CLI code or the asset tree into app bundles.
|
|
73
|
+
- Do **not** create a `bin/` or `scripts/` directory in the consumer.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
process.stderr.write(
|
|
76
|
-
`[claude-sync] missing or invalid "claude.assetPath" in ${resolve(packageRoot, 'package.json')}\n`,
|
|
77
|
-
);
|
|
78
|
-
process.exit(2);
|
|
79
|
-
}
|
|
75
|
+
### 2. Build
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
assetPath: pkg.claude.assetPath,
|
|
86
|
-
}).catch((err) => {
|
|
87
|
-
process.stderr.write(
|
|
88
|
-
`[${pkg.name}] claude-sync failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
89
|
-
);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
});
|
|
77
|
+
```bash
|
|
78
|
+
yarn build
|
|
79
|
+
# rolls up the library, emits types, then `claude-build-hashes` hashes every
|
|
80
|
+
# file under `claude.assetPath` and writes dist/claude-hashes.json
|
|
92
81
|
```
|
|
93
82
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
### 3. `scripts/build-hashes.mjs`
|
|
97
|
-
|
|
98
|
-
```javascript
|
|
99
|
-
#!/usr/bin/env node
|
|
100
|
-
import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
|
|
83
|
+
Ship the resulting `dist/` (including `claude-hashes.json`) alongside `docs/` when you publish.
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
const { outPath, fileCount } = await buildHashes();
|
|
104
|
-
console.log(`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`);
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error('❌ buildHashes failed:', err?.message ?? err);
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
```
|
|
85
|
+
### Rationale: `dependencies`, not `devDependencies`
|
|
110
86
|
|
|
111
|
-
|
|
87
|
+
- The monorepo build chain needs `.bin/claude-build-hashes` resolved, which requires the engine as a direct dep.
|
|
88
|
+
- For end users on npm / yarn-classic, listing the engine in `dependencies` makes `inject-claude-settings` transitively hoisted into `node_modules/.bin/`, enabling the short invocation `npx inject-claude-settings --package=<THIS>`. Pnpm strict users do not get the transitive hoist and must use the universal form `npx -p @slats/claude-assets-sync inject-claude-settings --package=<THIS>`.
|
|
89
|
+
- Bundle isolation is enforced by the import graph (`src/**` in the consumer never references the engine), not by dependency-type.
|
|
112
90
|
|
|
113
91
|
## Authoring `docs/claude/`
|
|
114
92
|
|
|
@@ -138,6 +116,12 @@ Every file under the asset root is hashed and tracked in `dist/claude-hashes.jso
|
|
|
138
116
|
- `--force` on TTY opens an interactive confirm via `@inquirer/prompts.confirm`, listing up to 3 diverged/orphan paths.
|
|
139
117
|
- `--force` on non-TTY prints the divergent list to stderr and proceeds.
|
|
140
118
|
|
|
119
|
+
## Architectural Invariants
|
|
120
|
+
|
|
121
|
+
- `src/core/**` never reads `package.json` or walks the filesystem. Only the `bin/` layer (and `src/commands/runCli/utils/resolvePackage.ts`, invoked from that dispatcher) is allowed to resolve a single explicitly-named target via `createRequire().resolve('${name}/package.json')`. Cross-package discovery (`--all`, workspace scan) is not supported.
|
|
122
|
+
- Prompts go through `@inquirer/prompts` only. No ink, no React.
|
|
123
|
+
- The engine assumes one consumer per invocation. That is the stable contract — extensions require explicit re-architecture.
|
|
124
|
+
|
|
141
125
|
## Programmatic API
|
|
142
126
|
|
|
143
127
|
```ts
|
|
@@ -156,7 +140,7 @@ See `src/index.ts` and `src/DETAIL.md` for the full export surface.
|
|
|
156
140
|
|
|
157
141
|
## Additional Docs
|
|
158
142
|
|
|
159
|
-
- `docs/consumer-integration.md` — complete consumer checklist (
|
|
143
|
+
- `docs/consumer-integration.md` — complete consumer checklist (package.json patches, verification steps, end-user install topologies)
|
|
160
144
|
- `docs/bundle-size-decision.md` — why `@inquirer/prompts` over ink
|
|
161
145
|
|
|
162
146
|
## License
|
package/dist/claude-hashes.json
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@slats/claude-assets-sync",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0"
|
|
6
6
|
},
|
|
7
|
-
"generatedAt": "2026-04-
|
|
7
|
+
"generatedAt": "2026-04-24T14:51:13.873Z",
|
|
8
8
|
"algorithm": "sha256",
|
|
9
9
|
"assetRoot": "docs/claude",
|
|
10
10
|
"files": {
|
|
11
|
-
"skills/claude-
|
|
12
|
-
"skills/claude-
|
|
13
|
-
"skills/claude-
|
|
14
|
-
"skills/claude-
|
|
15
|
-
"skills/claude-
|
|
16
|
-
"skills/claude-
|
|
17
|
-
"skills/claude-
|
|
11
|
+
"skills/claude-docs-asset-wiring/SKILL.md": "1984ed9ea93be0e7b8eec8f77d57c455e670989c3948546d017343e321fe1b80",
|
|
12
|
+
"skills/claude-docs-asset-wiring/knowledge/claude-md-template.md": "affd92189e9d367c8717068695aca8acbadf68989a4073a8c4b1e2f4a622b72d",
|
|
13
|
+
"skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md": "18e994bd16242f72a4dc6cfa508493f4b63853a7a16c4910c7b7abd0368bd103",
|
|
14
|
+
"skills/claude-docs-asset-wiring/knowledge/gotchas.md": "9957464329cc970f6717179fad907ffad45ee4433b1d6151e20b5b3bee75b775",
|
|
15
|
+
"skills/claude-docs-asset-wiring/knowledge/package-json-patches.md": "426dcfdfdd5c02405c3d8a753b800fc90e1e9286d6f1c3c29320cf13e2645878",
|
|
16
|
+
"skills/claude-docs-asset-wiring/knowledge/reference-files.md": "3a63a095826bbfd07fae232568faf4d415dbdae0970ee4466c962ba285c3a01d",
|
|
17
|
+
"skills/claude-docs-asset-wiring/knowledge/smoke-tests.md": "c90fc82d46bf4b55d794c2ce463b4c1e199d13d6b1161028486fb4bbbd6a60fe"
|
|
18
18
|
},
|
|
19
19
|
"previousVersions": {}
|
|
20
20
|
}
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { runCli, type DefaultFlags
|
|
1
|
+
export { runCli, type DefaultFlags } from './runCli/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { runCli } from './runCli.js';
|
|
2
|
-
export type { DefaultFlags
|
|
2
|
+
export type { DefaultFlags } from './type.js';
|
|
@@ -3,20 +3,33 @@
|
|
|
3
3
|
var commander = require('commander');
|
|
4
4
|
var logger = require('../../utils/logger.cjs');
|
|
5
5
|
var version = require('../../utils/version.cjs');
|
|
6
|
+
var resolveTargets = require('./utils/resolveTargets.cjs');
|
|
6
7
|
var runInject = require('./utils/runInject.cjs');
|
|
7
8
|
|
|
8
|
-
async function runCli(argv = process.argv
|
|
9
|
+
async function runCli(argv = process.argv) {
|
|
9
10
|
const cmd = new commander.Command();
|
|
10
11
|
cmd
|
|
11
|
-
.name('claude-
|
|
12
|
-
.description("Inject
|
|
13
|
-
.version(
|
|
12
|
+
.name('inject-claude-settings')
|
|
13
|
+
.description("Inject target consumer(s)' Claude assets into the selected .claude directory")
|
|
14
|
+
.version(version.VERSION)
|
|
15
|
+
.option('--package <name...>', 'Target(s). "@<scope>" = whole npm scope; "@<scope>/<name>" or "<name>" = one package. Repeat the flag or comma-separate values.', collectPackageValues, [])
|
|
14
16
|
.option('--scope <scope>', 'Target scope: user (~/.claude) | project (nearest ancestor .claude or <cwd>/.claude)')
|
|
15
17
|
.option('--dry-run', 'Preview without writing', false)
|
|
16
18
|
.option('--force', 'Overwrite user modifications', false)
|
|
17
19
|
.option('--root <path>', 'Override scope resolution cwd (default: cwd)')
|
|
18
20
|
.action(async (flags) => {
|
|
19
|
-
|
|
21
|
+
const targets = flags.package ?? [];
|
|
22
|
+
if (targets.length === 0) {
|
|
23
|
+
logger.logger.error('missing required flag: --package <name> (e.g. --package=@canard/schema-form or --package=@canard)');
|
|
24
|
+
process.exit(2);
|
|
25
|
+
}
|
|
26
|
+
const originCwd = flags.root ?? process.cwd();
|
|
27
|
+
const metadataList = await resolveTargets.resolveTargets(targets, originCwd);
|
|
28
|
+
if (metadataList.length === 0) {
|
|
29
|
+
logger.logger.warn(`no packages resolved from --package target(s): ${targets.join(', ')}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await runInject.runInject(flags, metadataList);
|
|
20
33
|
});
|
|
21
34
|
try {
|
|
22
35
|
await cmd.parseAsync([...argv]);
|
|
@@ -27,5 +40,14 @@ async function runCli(argv = process.argv, options) {
|
|
|
27
40
|
process.exit(1);
|
|
28
41
|
}
|
|
29
42
|
}
|
|
43
|
+
function collectPackageValues(value, previous = []) {
|
|
44
|
+
return [
|
|
45
|
+
...previous,
|
|
46
|
+
...value
|
|
47
|
+
.split(',')
|
|
48
|
+
.map((s) => s.trim())
|
|
49
|
+
.filter(Boolean),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
30
52
|
|
|
31
53
|
exports.runCli = runCli;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { RunCliOptions } from './type.js';
|
|
2
1
|
/**
|
|
3
2
|
* CLI entry for `@slats/claude-assets-sync`.
|
|
4
3
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The `inject-claude-settings` dispatcher parses `--package <name...>`
|
|
5
|
+
* from argv and classifies each value:
|
|
6
|
+
* - `@<scope>` — enumerate every workspace package under that scope
|
|
7
|
+
* - `@<scope>/<name>` — one scoped package
|
|
8
|
+
* - `<name>` — one unscoped package
|
|
9
|
+
*
|
|
10
|
+
* Targets are resolved via Node module resolution (`resolvePackage`)
|
|
11
|
+
* except for scope aliases, which are the only path allowed to walk
|
|
12
|
+
* the monorepo — that exception is isolated to `resolveScopeAlias.ts`.
|
|
9
13
|
*/
|
|
10
|
-
export declare function runCli(argv
|
|
14
|
+
export declare function runCli(argv?: readonly string[]): Promise<void>;
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { logger } from '../../utils/logger.mjs';
|
|
3
3
|
import { VERSION } from '../../utils/version.mjs';
|
|
4
|
+
import { resolveTargets } from './utils/resolveTargets.mjs';
|
|
4
5
|
import { runInject } from './utils/runInject.mjs';
|
|
5
6
|
|
|
6
|
-
async function runCli(argv = process.argv
|
|
7
|
+
async function runCli(argv = process.argv) {
|
|
7
8
|
const cmd = new Command();
|
|
8
9
|
cmd
|
|
9
|
-
.name('claude-
|
|
10
|
-
.description("Inject
|
|
11
|
-
.version(
|
|
10
|
+
.name('inject-claude-settings')
|
|
11
|
+
.description("Inject target consumer(s)' Claude assets into the selected .claude directory")
|
|
12
|
+
.version(VERSION)
|
|
13
|
+
.option('--package <name...>', 'Target(s). "@<scope>" = whole npm scope; "@<scope>/<name>" or "<name>" = one package. Repeat the flag or comma-separate values.', collectPackageValues, [])
|
|
12
14
|
.option('--scope <scope>', 'Target scope: user (~/.claude) | project (nearest ancestor .claude or <cwd>/.claude)')
|
|
13
15
|
.option('--dry-run', 'Preview without writing', false)
|
|
14
16
|
.option('--force', 'Overwrite user modifications', false)
|
|
15
17
|
.option('--root <path>', 'Override scope resolution cwd (default: cwd)')
|
|
16
18
|
.action(async (flags) => {
|
|
17
|
-
|
|
19
|
+
const targets = flags.package ?? [];
|
|
20
|
+
if (targets.length === 0) {
|
|
21
|
+
logger.error('missing required flag: --package <name> (e.g. --package=@canard/schema-form or --package=@canard)');
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
const originCwd = flags.root ?? process.cwd();
|
|
25
|
+
const metadataList = await resolveTargets(targets, originCwd);
|
|
26
|
+
if (metadataList.length === 0) {
|
|
27
|
+
logger.warn(`no packages resolved from --package target(s): ${targets.join(', ')}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
await runInject(flags, metadataList);
|
|
18
31
|
});
|
|
19
32
|
try {
|
|
20
33
|
await cmd.parseAsync([...argv]);
|
|
@@ -25,5 +38,14 @@ async function runCli(argv = process.argv, options) {
|
|
|
25
38
|
process.exit(1);
|
|
26
39
|
}
|
|
27
40
|
}
|
|
41
|
+
function collectPackageValues(value, previous = []) {
|
|
42
|
+
return [
|
|
43
|
+
...previous,
|
|
44
|
+
...value
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((s) => s.trim())
|
|
47
|
+
.filter(Boolean),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
28
50
|
|
|
29
51
|
export { runCli };
|
|
@@ -1,23 +1,14 @@
|
|
|
1
|
-
export interface RunCliOptions {
|
|
2
|
-
version?: string;
|
|
3
|
-
/** Absolute filesystem path to the consumer package root. */
|
|
4
|
-
packageRoot: string;
|
|
5
|
-
/** Consumer package name used in logs and error messages. */
|
|
6
|
-
packageName: string;
|
|
7
|
-
/** Consumer package version used in logs. */
|
|
8
|
-
packageVersion: string;
|
|
9
|
-
/** Asset directory path relative to `packageRoot`. */
|
|
10
|
-
assetPath: string;
|
|
11
|
-
}
|
|
12
1
|
export interface DefaultFlags {
|
|
13
2
|
scope?: string;
|
|
14
3
|
dryRun?: boolean;
|
|
15
4
|
force?: boolean;
|
|
16
5
|
root?: string;
|
|
6
|
+
package?: string[];
|
|
17
7
|
}
|
|
18
8
|
/**
|
|
19
9
|
* Resolved consumer metadata passed to the injection pipeline.
|
|
20
|
-
* The
|
|
10
|
+
* The dispatcher bin populates this by resolving a single explicitly-named
|
|
11
|
+
* target package — `core/**` still never reads `package.json` itself.
|
|
21
12
|
*/
|
|
22
13
|
export interface ConsumerPackage {
|
|
23
14
|
name: string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const NPM_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
4
|
+
function classifyTarget(target) {
|
|
5
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
6
|
+
return { kind: 'invalid', reason: 'empty --package value' };
|
|
7
|
+
}
|
|
8
|
+
if (target.startsWith('@')) {
|
|
9
|
+
const body = target.slice(1);
|
|
10
|
+
const slashIndex = body.indexOf('/');
|
|
11
|
+
if (slashIndex === -1) {
|
|
12
|
+
if (!NPM_NAME_PATTERN.test(body)) {
|
|
13
|
+
return {
|
|
14
|
+
kind: 'invalid',
|
|
15
|
+
reason: `invalid scope alias "${target}" — expected "@<scope>" with lowercase alphanumerics, ".", "-", or "_"`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return { kind: 'scope', scope: body };
|
|
19
|
+
}
|
|
20
|
+
const scopePart = body.slice(0, slashIndex);
|
|
21
|
+
const namePart = body.slice(slashIndex + 1);
|
|
22
|
+
if (!NPM_NAME_PATTERN.test(scopePart) ||
|
|
23
|
+
namePart.length === 0 ||
|
|
24
|
+
namePart.includes('/') ||
|
|
25
|
+
!NPM_NAME_PATTERN.test(namePart)) {
|
|
26
|
+
return {
|
|
27
|
+
kind: 'invalid',
|
|
28
|
+
reason: `invalid scoped package "${target}" — expected "@<scope>/<name>"`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { kind: 'package', name: target };
|
|
32
|
+
}
|
|
33
|
+
if (target.includes('/')) {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'invalid',
|
|
36
|
+
reason: `invalid target "${target}" — unscoped package names cannot contain "/"`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!NPM_NAME_PATTERN.test(target)) {
|
|
40
|
+
return {
|
|
41
|
+
kind: 'invalid',
|
|
42
|
+
reason: `invalid package name "${target}" — expected lowercase alphanumerics, ".", "-", or "_"`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { kind: 'package', name: target };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
exports.classifyTarget = classifyTarget;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ClassifiedTarget = {
|
|
2
|
+
kind: 'scope';
|
|
3
|
+
scope: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'package';
|
|
6
|
+
name: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'invalid';
|
|
9
|
+
reason: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Classify a `--package` value as a scope alias, a package name, or invalid.
|
|
13
|
+
*
|
|
14
|
+
* - `@<scope>` (no slash) — all packages under that npm scope
|
|
15
|
+
* - `@<scope>/<name>` — one scoped package
|
|
16
|
+
* - `<name>` (no `@`, no slash) — one unscoped package
|
|
17
|
+
* - anything else → invalid
|
|
18
|
+
*/
|
|
19
|
+
export declare function classifyTarget(target: string): ClassifiedTarget;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const NPM_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
2
|
+
function classifyTarget(target) {
|
|
3
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
4
|
+
return { kind: 'invalid', reason: 'empty --package value' };
|
|
5
|
+
}
|
|
6
|
+
if (target.startsWith('@')) {
|
|
7
|
+
const body = target.slice(1);
|
|
8
|
+
const slashIndex = body.indexOf('/');
|
|
9
|
+
if (slashIndex === -1) {
|
|
10
|
+
if (!NPM_NAME_PATTERN.test(body)) {
|
|
11
|
+
return {
|
|
12
|
+
kind: 'invalid',
|
|
13
|
+
reason: `invalid scope alias "${target}" — expected "@<scope>" with lowercase alphanumerics, ".", "-", or "_"`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return { kind: 'scope', scope: body };
|
|
17
|
+
}
|
|
18
|
+
const scopePart = body.slice(0, slashIndex);
|
|
19
|
+
const namePart = body.slice(slashIndex + 1);
|
|
20
|
+
if (!NPM_NAME_PATTERN.test(scopePart) ||
|
|
21
|
+
namePart.length === 0 ||
|
|
22
|
+
namePart.includes('/') ||
|
|
23
|
+
!NPM_NAME_PATTERN.test(namePart)) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'invalid',
|
|
26
|
+
reason: `invalid scoped package "${target}" — expected "@<scope>/<name>"`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { kind: 'package', name: target };
|
|
30
|
+
}
|
|
31
|
+
if (target.includes('/')) {
|
|
32
|
+
return {
|
|
33
|
+
kind: 'invalid',
|
|
34
|
+
reason: `invalid target "${target}" — unscoped package names cannot contain "/"`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!NPM_NAME_PATTERN.test(target)) {
|
|
38
|
+
return {
|
|
39
|
+
kind: 'invalid',
|
|
40
|
+
reason: `invalid package name "${target}" — expected lowercase alphanumerics, ".", "-", or "_"`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { kind: 'package', name: target };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { classifyTarget };
|
|
@@ -15,7 +15,7 @@ var logger = require('../../../utils/logger.cjs');
|
|
|
15
15
|
async function injectOne(target, scope, flags, originCwd) {
|
|
16
16
|
if (!target.hashesPresent) {
|
|
17
17
|
logger.logger.warn(`${target.name}: dist/claude-hashes.json missing — build the package (e.g. yarn build) to regenerate the hash manifest first.`);
|
|
18
|
-
return;
|
|
18
|
+
return 0;
|
|
19
19
|
}
|
|
20
20
|
logger.logger.heading(`${target.name}@${target.version}`);
|
|
21
21
|
const stopHeartbeat = heartbeat.startHeartbeat({
|
|
@@ -37,8 +37,7 @@ async function injectOne(target, scope, flags, originCwd) {
|
|
|
37
37
|
return confirmForce.confirmForceAsync(diverged.length, orphans.length, [...diverged, ...orphans].map((a) => a.relPath).slice(0, 3));
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
|
-
|
|
41
|
-
process.exit(report.exitCode);
|
|
40
|
+
return report.exitCode;
|
|
42
41
|
}
|
|
43
42
|
finally {
|
|
44
43
|
stopHeartbeat();
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type Scope } from '../../../core/index.js';
|
|
2
2
|
import type { ConsumerPackage, DefaultFlags } from '../type.js';
|
|
3
|
-
export declare function injectOne(target: ConsumerPackage, scope: Scope, flags: DefaultFlags, originCwd: string): Promise<
|
|
3
|
+
export declare function injectOne(target: ConsumerPackage, scope: Scope, flags: DefaultFlags, originCwd: string): Promise<number>;
|
|
@@ -13,7 +13,7 @@ import { logger } from '../../../utils/logger.mjs';
|
|
|
13
13
|
async function injectOne(target, scope, flags, originCwd) {
|
|
14
14
|
if (!target.hashesPresent) {
|
|
15
15
|
logger.warn(`${target.name}: dist/claude-hashes.json missing — build the package (e.g. yarn build) to regenerate the hash manifest first.`);
|
|
16
|
-
return;
|
|
16
|
+
return 0;
|
|
17
17
|
}
|
|
18
18
|
logger.heading(`${target.name}@${target.version}`);
|
|
19
19
|
const stopHeartbeat = startHeartbeat({
|
|
@@ -35,8 +35,7 @@ async function injectOne(target, scope, flags, originCwd) {
|
|
|
35
35
|
return confirmForceAsync(diverged.length, orphans.length, [...diverged, ...orphans].map((a) => a.relPath).slice(0, 3));
|
|
36
36
|
},
|
|
37
37
|
});
|
|
38
|
-
|
|
39
|
-
process.exit(report.exitCode);
|
|
38
|
+
return report.exitCode;
|
|
40
39
|
}
|
|
41
40
|
finally {
|
|
42
41
|
stopHeartbeat();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_fs = require('node:fs');
|
|
4
|
+
var promises = require('node:fs/promises');
|
|
5
|
+
var node_module = require('node:module');
|
|
6
|
+
var node_path = require('node:path');
|
|
7
|
+
var logger = require('../../../utils/logger.cjs');
|
|
8
|
+
|
|
9
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
|
+
async function resolvePackage(name, options = {}) {
|
|
11
|
+
const pkgJsonPath = resolvePackageJsonPath(name);
|
|
12
|
+
if (!pkgJsonPath) {
|
|
13
|
+
logger.logger.error(`cannot resolve package "${name}". Install it in the current project or pass the correct name.`);
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
const packageRoot = node_path.dirname(pkgJsonPath);
|
|
17
|
+
const raw = await promises.readFile(pkgJsonPath, 'utf-8');
|
|
18
|
+
const pkg = JSON.parse(raw);
|
|
19
|
+
if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
|
|
20
|
+
if (options.skipMissingAsset) {
|
|
21
|
+
logger.logger.warn(`"${name}" package.json is missing a string "name" or "version" — skipping.`);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
logger.logger.error(`${pkgJsonPath} must define string "name" and "version".`);
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
const assetPath = pkg.claude?.assetPath;
|
|
28
|
+
if (typeof assetPath !== 'string' || assetPath.length === 0) {
|
|
29
|
+
if (options.skipMissingAsset) {
|
|
30
|
+
logger.logger.warn(`"${name}" is missing "claude.assetPath" — skipping (the package does not ship Claude assets).`);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
logger.logger.error(`"${name}" is missing "claude.assetPath" in its package.json — the package does not ship Claude assets.`);
|
|
34
|
+
process.exit(2);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
packageRoot,
|
|
38
|
+
packageName: pkg.name,
|
|
39
|
+
packageVersion: pkg.version,
|
|
40
|
+
assetPath,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function resolvePackageJsonPath(name) {
|
|
44
|
+
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('commands/runCli/utils/resolvePackage.cjs', document.baseURI).href)));
|
|
45
|
+
try {
|
|
46
|
+
return require$1.resolve(`${name}/package.json`);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const code = err?.code;
|
|
50
|
+
if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED')
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
let mainEntry;
|
|
54
|
+
try {
|
|
55
|
+
mainEntry = require$1.resolve(name);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
let dir = node_path.dirname(mainEntry);
|
|
61
|
+
while (dir && dir !== node_path.dirname(dir)) {
|
|
62
|
+
const candidate = node_path.resolve(dir, 'package.json');
|
|
63
|
+
if (node_fs.existsSync(candidate)) {
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse(node_fs.readFileSync(candidate, 'utf-8'));
|
|
66
|
+
if (pkg.name === name)
|
|
67
|
+
return candidate;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
dir = node_path.dirname(dir);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.resolvePackage = resolvePackage;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ResolvedMetadata {
|
|
2
|
+
packageRoot: string;
|
|
3
|
+
packageName: string;
|
|
4
|
+
packageVersion: string;
|
|
5
|
+
assetPath: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvePackageOptions {
|
|
8
|
+
/**
|
|
9
|
+
* When `true`, a package without `claude.assetPath` is warned and the
|
|
10
|
+
* function returns `null` instead of calling `process.exit`. Default
|
|
11
|
+
* `false` preserves the v0.3.0 strict behavior for single-target
|
|
12
|
+
* dispatcher calls.
|
|
13
|
+
*/
|
|
14
|
+
skipMissingAsset?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolvePackage(name: string, options?: ResolvePackageOptions): Promise<ResolvedMetadata | null>;
|