@slats/claude-assets-sync 0.1.4 → 0.2.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 +111 -587
- package/bin/claude-sync.mjs +24 -0
- package/dist/claude-hashes.json +20 -0
- package/dist/commands/index.d.ts +1 -112
- package/dist/commands/runCli/index.d.ts +2 -0
- package/dist/commands/runCli/runCli.cjs +31 -0
- package/dist/commands/runCli/runCli.d.ts +10 -0
- package/dist/commands/runCli/runCli.mjs +29 -0
- package/dist/commands/runCli/type.d.ts +28 -0
- package/dist/commands/runCli/utils/injectOne.cjs +48 -0
- package/dist/commands/runCli/utils/injectOne.d.ts +3 -0
- package/dist/commands/runCli/utils/injectOne.mjs +46 -0
- package/dist/commands/runCli/utils/resolveScopeFlag.cjs +28 -0
- package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +2 -0
- package/dist/commands/runCli/utils/resolveScopeFlag.mjs +26 -0
- package/dist/commands/runCli/utils/runInject.cjs +36 -0
- package/dist/commands/runCli/utils/runInject.d.ts +2 -0
- package/dist/commands/runCli/utils/runInject.mjs +34 -0
- package/dist/core/buildPlan/buildPlan.cjs +42 -0
- package/dist/core/buildPlan/buildPlan.d.ts +2 -0
- package/dist/core/buildPlan/buildPlan.mjs +40 -0
- package/dist/core/buildPlan/index.d.ts +2 -0
- package/dist/core/buildPlan/type.d.ts +32 -0
- package/dist/core/buildPlan/utils/toPosix.cjs +9 -0
- package/dist/core/buildPlan/utils/toPosix.d.ts +1 -0
- package/dist/core/buildPlan/utils/toPosix.mjs +7 -0
- package/dist/core/buildPlan/utils/walkFiles.cjs +25 -0
- package/dist/core/buildPlan/utils/walkFiles.d.ts +1 -0
- package/dist/core/buildPlan/utils/walkFiles.mjs +23 -0
- package/dist/core/hash/hash.cjs +30 -0
- package/dist/core/hash/hash.d.ts +4 -0
- package/dist/core/hash/hash.mjs +26 -0
- package/dist/core/hash/index.d.ts +1 -0
- package/dist/core/hashManifest/hashManifest.cjs +27 -0
- package/dist/core/hashManifest/hashManifest.d.ts +17 -0
- package/dist/core/hashManifest/hashManifest.mjs +23 -0
- package/dist/core/hashManifest/index.d.ts +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/injectDocs/index.d.ts +2 -0
- package/dist/core/injectDocs/injectDocs.cjs +43 -0
- package/dist/core/injectDocs/injectDocs.d.ts +2 -0
- package/dist/core/injectDocs/injectDocs.mjs +41 -0
- package/dist/core/injectDocs/type.d.ts +30 -0
- package/dist/core/injectDocs/utils/applyAction.cjs +21 -0
- package/dist/core/injectDocs/utils/applyAction.d.ts +2 -0
- package/dist/core/injectDocs/utils/applyAction.mjs +19 -0
- package/dist/core/injectDocs/utils/emitCiForceList.cjs +10 -0
- package/dist/core/injectDocs/utils/emitCiForceList.d.ts +2 -0
- package/dist/core/injectDocs/utils/emitCiForceList.mjs +8 -0
- package/dist/core/injectDocs/utils/printPlan.cjs +20 -0
- package/dist/core/injectDocs/utils/printPlan.d.ts +2 -0
- package/dist/core/injectDocs/utils/printPlan.mjs +18 -0
- package/dist/core/injectDocs/utils/summarize.cjs +27 -0
- package/dist/core/injectDocs/utils/summarize.d.ts +3 -0
- package/dist/core/injectDocs/utils/summarize.mjs +25 -0
- package/dist/core/scope/index.d.ts +1 -0
- package/dist/core/scope/scope.cjs +46 -0
- package/dist/core/scope/scope.d.ts +16 -0
- package/dist/core/scope/scope.mjs +41 -0
- package/dist/core/scope/utils/isDirectory.cjs +14 -0
- package/dist/core/scope/utils/isDirectory.d.ts +1 -0
- package/dist/core/scope/utils/isDirectory.mjs +12 -0
- package/dist/index.cjs +15 -9
- package/dist/index.d.ts +3 -5
- package/dist/index.mjs +7 -3
- package/dist/prompts/confirmForce.cjs +27 -0
- package/dist/prompts/confirmForce.d.ts +1 -0
- package/dist/prompts/confirmForce.mjs +25 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/selectScope.cjs +30 -0
- package/dist/prompts/selectScope.d.ts +2 -0
- package/dist/prompts/selectScope.mjs +28 -0
- package/dist/utils/heartbeat.cjs +25 -0
- package/dist/utils/heartbeat.d.ts +16 -0
- package/dist/utils/heartbeat.mjs +23 -0
- package/dist/utils/logger.cjs +7 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.mjs +7 -0
- package/dist/utils/types.d.ts +1 -252
- package/dist/utils/version.cjs +2 -14
- package/dist/utils/version.d.ts +3 -53
- package/dist/utils/version.mjs +2 -13
- package/docs/bundle-size-decision.md +36 -0
- package/docs/claude/skills/claude-sync-applier/SKILL.md +195 -0
- package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +77 -0
- package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +126 -0
- package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +139 -0
- package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +130 -0
- package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +120 -0
- package/docs/claude/skills/claude-sync-applier/knowledge/smoke-tests.md +102 -0
- package/docs/consumer-integration.md +153 -0
- package/package.json +24 -16
- package/scripts/build-hashes.mjs +30 -0
- package/scripts/buildHashes.d.mts +15 -0
- package/scripts/buildHashes.mjs +82 -0
- package/scripts/claude-build-hashes.mjs +42 -0
- package/scripts/inject-version.js +112 -0
- package/dist/cli.cjs +0 -8
- package/dist/cli.d.ts +0 -1
- package/dist/cli.mjs +0 -7
- package/dist/commands/add.cjs +0 -80
- package/dist/commands/add.d.ts +0 -8
- package/dist/commands/add.mjs +0 -78
- package/dist/commands/list.cjs +0 -94
- package/dist/commands/list.d.ts +0 -15
- package/dist/commands/list.mjs +0 -91
- package/dist/commands/migrate.cjs +0 -9
- package/dist/commands/migrate.d.ts +0 -6
- package/dist/commands/migrate.mjs +0 -7
- package/dist/commands/remove.cjs +0 -127
- package/dist/commands/remove.d.ts +0 -6
- package/dist/commands/remove.mjs +0 -105
- package/dist/commands/status.cjs +0 -193
- package/dist/commands/status.d.ts +0 -6
- package/dist/commands/status.mjs +0 -171
- package/dist/commands/sync.cjs +0 -28
- package/dist/commands/sync.d.ts +0 -6
- package/dist/commands/sync.mjs +0 -26
- package/dist/commands/types.d.ts +0 -89
- package/dist/commands/update.cjs +0 -209
- package/dist/commands/update.d.ts +0 -29
- package/dist/commands/update.mjs +0 -206
- package/dist/components/add/AddCommand.cjs +0 -103
- package/dist/components/add/AddCommand.d.ts +0 -14
- package/dist/components/add/AddCommand.mjs +0 -101
- package/dist/components/add/BulkAddView.cjs +0 -165
- package/dist/components/add/BulkAddView.d.ts +0 -11
- package/dist/components/add/BulkAddView.mjs +0 -163
- package/dist/components/add/index.d.ts +0 -2
- package/dist/components/index.d.ts +0 -2
- package/dist/components/list/EditableTreeItem.d.ts +0 -13
- package/dist/components/list/ListCommand.cjs +0 -651
- package/dist/components/list/ListCommand.d.ts +0 -5
- package/dist/components/list/ListCommand.mjs +0 -649
- package/dist/components/list/SyncedPackageTree.d.ts +0 -14
- package/dist/components/list/index.d.ts +0 -10
- package/dist/components/list/types.d.ts +0 -14
- package/dist/components/primitives/Box.d.ts +0 -4
- package/dist/components/primitives/Spinner.d.ts +0 -6
- package/dist/components/primitives/Text.d.ts +0 -4
- package/dist/components/primitives/index.d.ts +0 -3
- package/dist/components/remove/RemoveConfirm.cjs +0 -18
- package/dist/components/remove/RemoveConfirm.d.ts +0 -11
- package/dist/components/remove/RemoveConfirm.mjs +0 -16
- package/dist/components/shared/Confirm.cjs +0 -30
- package/dist/components/shared/Confirm.d.ts +0 -8
- package/dist/components/shared/Confirm.mjs +0 -28
- package/dist/components/shared/MenuItem.cjs +0 -18
- package/dist/components/shared/MenuItem.d.ts +0 -7
- package/dist/components/shared/MenuItem.mjs +0 -16
- package/dist/components/shared/ProgressBar.d.ts +0 -7
- package/dist/components/shared/StepRunner.cjs +0 -58
- package/dist/components/shared/StepRunner.d.ts +0 -15
- package/dist/components/shared/StepRunner.mjs +0 -56
- package/dist/components/shared/Table.cjs +0 -19
- package/dist/components/shared/Table.d.ts +0 -8
- package/dist/components/shared/Table.mjs +0 -17
- package/dist/components/shared/index.d.ts +0 -6
- package/dist/components/status/PackageStatusCard.d.ts +0 -10
- package/dist/components/status/StatusDisplay.cjs +0 -26
- package/dist/components/status/StatusDisplay.d.ts +0 -23
- package/dist/components/status/StatusDisplay.mjs +0 -24
- package/dist/components/status/StatusTreeNode.cjs +0 -40
- package/dist/components/status/StatusTreeNode.d.ts +0 -15
- package/dist/components/status/StatusTreeNode.mjs +0 -38
- package/dist/components/status/index.d.ts +0 -6
- package/dist/components/tree/AssetTreeNode.cjs +0 -54
- package/dist/components/tree/AssetTreeNode.d.ts +0 -12
- package/dist/components/tree/AssetTreeNode.mjs +0 -52
- package/dist/components/tree/TreeSelect.cjs +0 -129
- package/dist/components/tree/TreeSelect.d.ts +0 -12
- package/dist/components/tree/TreeSelect.mjs +0 -127
- package/dist/components/tree/index.d.ts +0 -4
- package/dist/core/assetStructure.cjs +0 -30
- package/dist/core/assetStructure.d.ts +0 -36
- package/dist/core/assetStructure.mjs +0 -27
- package/dist/core/cli.cjs +0 -106
- package/dist/core/cli.d.ts +0 -9
- package/dist/core/cli.mjs +0 -103
- package/dist/core/constants.cjs +0 -28
- package/dist/core/constants.d.ts +0 -94
- package/dist/core/constants.mjs +0 -21
- package/dist/core/filesystem.cjs +0 -98
- package/dist/core/filesystem.d.ts +0 -94
- package/dist/core/filesystem.mjs +0 -88
- package/dist/core/github.cjs +0 -115
- package/dist/core/github.d.ts +0 -61
- package/dist/core/github.mjs +0 -107
- package/dist/core/io.cjs +0 -46
- package/dist/core/io.d.ts +0 -40
- package/dist/core/io.mjs +0 -39
- package/dist/core/listOperations.cjs +0 -228
- package/dist/core/listOperations.d.ts +0 -43
- package/dist/core/listOperations.mjs +0 -205
- package/dist/core/localSource.cjs +0 -126
- package/dist/core/localSource.d.ts +0 -33
- package/dist/core/localSource.mjs +0 -120
- package/dist/core/migration.cjs +0 -201
- package/dist/core/migration.d.ts +0 -57
- package/dist/core/migration.mjs +0 -198
- package/dist/core/packageScanner.cjs +0 -360
- package/dist/core/packageScanner.d.ts +0 -22
- package/dist/core/packageScanner.mjs +0 -356
- package/dist/core/sync.cjs +0 -400
- package/dist/core/sync.d.ts +0 -21
- package/dist/core/sync.mjs +0 -397
- package/dist/core/syncMeta.cjs +0 -242
- package/dist/core/syncMeta.d.ts +0 -75
- package/dist/core/syncMeta.mjs +0 -229
- package/dist/utils/dependencies.cjs +0 -57
- package/dist/utils/dependencies.d.ts +0 -10
- package/dist/utils/dependencies.mjs +0 -34
- package/dist/utils/nameTransform.cjs +0 -13
- package/dist/utils/nameTransform.d.ts +0 -65
- package/dist/utils/nameTransform.mjs +0 -11
- package/dist/utils/package.cjs +0 -170
- package/dist/utils/package.d.ts +0 -105
- package/dist/utils/package.mjs +0 -157
- package/dist/utils/packageName.cjs +0 -24
- package/dist/utils/packageName.d.ts +0 -32
- package/dist/utils/packageName.mjs +0 -21
- package/dist/utils/paths.cjs +0 -18
- package/dist/utils/paths.d.ts +0 -55
- package/dist/utils/paths.mjs +0 -15
- package/dist/version.cjs +0 -5
- package/dist/version.d.ts +0 -5
- package/dist/version.mjs +0 -3
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Reference Files
|
|
2
|
+
|
|
3
|
+
The two stub files are **identical across all consumers**. Copy verbatim, do
|
|
4
|
+
no substitution — the stubs discover package metadata at runtime via
|
|
5
|
+
`import.meta.url`.
|
|
6
|
+
|
|
7
|
+
Reference consumer: `packages/canard/schema-form`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## `bin/claude-sync.mjs`
|
|
12
|
+
|
|
13
|
+
Source of truth: `packages/canard/schema-form/bin/claude-sync.mjs`.
|
|
14
|
+
|
|
15
|
+
Expected content (must match exactly):
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
#!/usr/bin/env node
|
|
19
|
+
import { runCli } from '@slats/claude-assets-sync';
|
|
20
|
+
import { readFile } from 'node:fs/promises';
|
|
21
|
+
import { dirname, resolve } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
+
const pkg = JSON.parse(
|
|
26
|
+
await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (typeof pkg.claude?.assetPath === 'string') {
|
|
30
|
+
runCli(process.argv, {
|
|
31
|
+
packageRoot,
|
|
32
|
+
packageName: pkg.name,
|
|
33
|
+
packageVersion: pkg.version,
|
|
34
|
+
assetPath: pkg.claude.assetPath,
|
|
35
|
+
}).catch((err) => {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`[${pkg.name}] claude-sync failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Notes**
|
|
45
|
+
|
|
46
|
+
- `import.meta.url` is resolved at runtime to the installed file path, so
|
|
47
|
+
`packageRoot` works both during local development and when consumed as a
|
|
48
|
+
dependency.
|
|
49
|
+
- `pkg.claude?.assetPath` missing → the stub is a silent no-op. That is the
|
|
50
|
+
feature that lets a package opt out without shipping broken bins.
|
|
51
|
+
- Emit to stderr on failure and exit 1. Do not `throw` — the CLI is the
|
|
52
|
+
process boundary.
|
|
53
|
+
|
|
54
|
+
After writing, make it executable:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
chmod +x ${TARGET_PATH}/bin/claude-sync.mjs
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## `scripts/build-hashes.mjs`
|
|
63
|
+
|
|
64
|
+
Source of truth: `packages/canard/schema-form/scripts/build-hashes.mjs`.
|
|
65
|
+
|
|
66
|
+
Expected content (must match exactly):
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
#!/usr/bin/env node
|
|
70
|
+
// Thin wrapper — parses this package's package.json and delegates to
|
|
71
|
+
// @slats/claude-assets-sync/buildHashes. The `claude.assetPath` convention
|
|
72
|
+
// lives here, in the consumer; the library is generic.
|
|
73
|
+
import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
|
|
74
|
+
import { readFile } from 'node:fs/promises';
|
|
75
|
+
import { resolve } from 'node:path';
|
|
76
|
+
|
|
77
|
+
const packageRoot = process.cwd();
|
|
78
|
+
const pkg = JSON.parse(
|
|
79
|
+
await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (typeof pkg.claude?.assetPath === 'string') {
|
|
83
|
+
try {
|
|
84
|
+
const { outPath, fileCount } = await buildHashes({
|
|
85
|
+
packageRoot,
|
|
86
|
+
packageName: pkg.name,
|
|
87
|
+
packageVersion: pkg.version,
|
|
88
|
+
assetPath: pkg.claude.assetPath,
|
|
89
|
+
});
|
|
90
|
+
console.log(
|
|
91
|
+
`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`,
|
|
92
|
+
);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('❌ build-hashes failed:', err?.message ?? err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Notes**
|
|
101
|
+
|
|
102
|
+
- `packageRoot = process.cwd()` intentionally — this script runs via
|
|
103
|
+
`yarn <shortcut> build:hashes` from the package directory.
|
|
104
|
+
- Same no-op behavior when `claude.assetPath` is missing.
|
|
105
|
+
- Emits to stdout on success (humans read it during the build).
|
|
106
|
+
|
|
107
|
+
No `chmod +x` needed — it's invoked via `node scripts/build-hashes.mjs`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Verification
|
|
112
|
+
|
|
113
|
+
Before declaring Step 1/Step 2 complete, diff against the reference:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
diff packages/canard/schema-form/bin/claude-sync.mjs ${TARGET_PATH}/bin/claude-sync.mjs
|
|
117
|
+
diff packages/canard/schema-form/scripts/build-hashes.mjs ${TARGET_PATH}/scripts/build-hashes.mjs
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Both diffs must be empty.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# E2E Smoke Tests — 6-path matrix
|
|
2
|
+
|
|
3
|
+
**Run from `/tmp/...` — never from the monorepo root or `${TARGET_PATH}/`.**
|
|
4
|
+
|
|
5
|
+
`--scope=project` walks `cwd` upward looking for an existing `.claude`
|
|
6
|
+
directory. Running from the monorepo would reuse or mutate the real repo's
|
|
7
|
+
`.claude`, which is a destructive error.
|
|
8
|
+
|
|
9
|
+
No fake `node_modules` needed — the bin resolves everything via
|
|
10
|
+
`import.meta.url`.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
BIN=$PWD/${TARGET_PATH}/bin/claude-sync.mjs
|
|
18
|
+
DIR=/tmp/claude-sync-smoke-${SHORTCUT:-target}
|
|
19
|
+
[ -d "$DIR" ] && find "$DIR" -mindepth 1 -delete
|
|
20
|
+
mkdir -p "$DIR" && cd "$DIR"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`[ -d ... ] && find -delete` keeps the setup idempotent. **Never** use
|
|
24
|
+
`rm -rf` or unquoted `*` globs — too easy to nuke the wrong directory.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Matrix
|
|
29
|
+
|
|
30
|
+
Execute sequentially. `EXIT=$?` after each so the value is captured before the
|
|
31
|
+
next command overwrites `$?`.
|
|
32
|
+
|
|
33
|
+
| # | Command | Expected exit | Purpose |
|
|
34
|
+
|---|-----------------------------------------------------------------------------|---------------|----------------------------------------------------------|
|
|
35
|
+
| 1 | `node "$BIN" --scope=project --dry-run` | 0 | Dry run on empty dir — previews actions, no writes. |
|
|
36
|
+
| 2 | `node "$BIN" --scope=project` | 0 | First real install — writes `.claude/` under `$DIR`. |
|
|
37
|
+
| 3 | `node "$BIN" --scope=project` | 0 | Re-run — no-op because everything is already up-to-date. |
|
|
38
|
+
| 4 | (after tampering) `CI=true node "$BIN" --scope=project` | **2** | CI + tampered content → refuse to overwrite. |
|
|
39
|
+
| 5 | `CI=true node "$BIN" --scope=project --force` | 0 | `--force` overrides the refusal. |
|
|
40
|
+
| 6 | `CI=true node "$BIN"` | **2** | Missing `--scope` in non-TTY context → exit 2. |
|
|
41
|
+
|
|
42
|
+
### Tamper step (between path 3 and path 4)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
find .claude -name SKILL.md -exec sh -c 'echo tampered >> "$1"' _ {} \;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Appends `tampered` to every `SKILL.md` under the local `.claude/`. This
|
|
49
|
+
simulates a human edit that the CI-mode bin must detect and refuse to clobber.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Execution Shape
|
|
54
|
+
|
|
55
|
+
Split into **two bash calls** because `cwd` resets between Bash tool
|
|
56
|
+
invocations.
|
|
57
|
+
|
|
58
|
+
**First call** — paths 1–3:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
BIN=$PWD/${TARGET_PATH}/bin/claude-sync.mjs
|
|
62
|
+
DIR=/tmp/claude-sync-smoke-${SHORTCUT:-target}
|
|
63
|
+
[ -d "$DIR" ] && find "$DIR" -mindepth 1 -delete
|
|
64
|
+
mkdir -p "$DIR" && cd "$DIR"
|
|
65
|
+
|
|
66
|
+
node "$BIN" --scope=project --dry-run; echo "EXIT=$?" # expect 0
|
|
67
|
+
node "$BIN" --scope=project; echo "EXIT=$?" # expect 0
|
|
68
|
+
node "$BIN" --scope=project; echo "EXIT=$?" # expect 0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Second call** — paths 4–6 (re-enter `$DIR`, reuse state from first call):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
DIR=/tmp/claude-sync-smoke-${SHORTCUT:-target}
|
|
75
|
+
BIN=$PWD/${TARGET_PATH}/bin/claude-sync.mjs
|
|
76
|
+
cd "$DIR"
|
|
77
|
+
|
|
78
|
+
find .claude -name SKILL.md -exec sh -c 'echo tampered >> "$1"' _ {} \;
|
|
79
|
+
CI=true node "$BIN" --scope=project; echo "EXIT=$?" # expect 2
|
|
80
|
+
CI=true node "$BIN" --scope=project --force; echo "EXIT=$?" # expect 0
|
|
81
|
+
CI=true node "$BIN"; echo "EXIT=$?" # expect 2
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Note: `$PWD` in the second call is the parent shell's cwd (monorepo root), so
|
|
85
|
+
`BIN` resolves correctly. `cd "$DIR"` then moves into the smoke directory
|
|
86
|
+
before invoking.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Failure Handling
|
|
91
|
+
|
|
92
|
+
| Observed | Meaning | Action |
|
|
93
|
+
|----------|-------------------------------------------------------------------------|--------------------------------------------------------------------|
|
|
94
|
+
| 1 ≠ 0 | Dry-run crashed. Likely a stub or engine bug. | Stop, capture stderr, report. |
|
|
95
|
+
| 2 ≠ 0 | First write failed. Likely permissions, engine bug, or manifest issue. | Stop, inspect `dist/claude-hashes.json`, report. |
|
|
96
|
+
| 3 ≠ 0 | Idempotency broken — re-run should be no-op. | Stop, diff `$DIR/.claude` before/after, report. |
|
|
97
|
+
| 4 = 0 | CI mode did not refuse tampered files. Safety regression. | Stop — the engine's CI gate is broken. |
|
|
98
|
+
| 5 ≠ 0 | `--force` failed to override. Check engine. | Stop, report. |
|
|
99
|
+
| 6 = 0 | Engine defaulted a scope in non-TTY context. Should require `--scope`. | Stop, report. |
|
|
100
|
+
|
|
101
|
+
Do not attempt to "make the tests pass" by altering expectations. The matrix
|
|
102
|
+
encodes invariants of the engine — a mismatch is a real regression upstream.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Consumer Integration Template
|
|
2
|
+
|
|
3
|
+
How to make a package "claude-sync aware" so `claude-sync` discovers and injects its `docs/claude/` tree into end-user `.claude` directories.
|
|
4
|
+
|
|
5
|
+
## 1. `package.json` additions
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
{
|
|
9
|
+
"name": "@your-scope/your-package",
|
|
10
|
+
"version": "…",
|
|
11
|
+
"bin": {
|
|
12
|
+
"claude-sync": "./bin/claude-sync.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"docs",
|
|
17
|
+
"dist/claude-hashes.json",
|
|
18
|
+
"bin",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "… && yarn build:hashes",
|
|
23
|
+
"build:hashes": "node scripts/build-hashes.mjs"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@slats/claude-assets-sync": "workspace:^",
|
|
27
|
+
},
|
|
28
|
+
"claude": {
|
|
29
|
+
"assetPath": "docs/claude"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Do **not** expose `./bin/*` in `exports`. That would let consumer bundlers accidentally pull CLI code into app bundles.
|
|
35
|
+
|
|
36
|
+
## 2. `bin/claude-sync.mjs` (3-line re-export stub)
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
#!/usr/bin/env node
|
|
40
|
+
import { runCli } from '@slats/claude-assets-sync';
|
|
41
|
+
|
|
42
|
+
runCli(process.argv, { invokedFromBin: import.meta.url }).catch((err) => {
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
`[@your-scope/your-package] claude-sync failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
45
|
+
);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`runCli` determines the implicit `--package` target in this priority order:
|
|
51
|
+
|
|
52
|
+
1. `--all` or `--package=<name>` (explicit)
|
|
53
|
+
2. The consumer that owns `process.cwd()` (i.e. the terminal you launched from). When you `cd` into a consumer's package directory and run `yarn claude-sync`, that consumer is picked automatically.
|
|
54
|
+
3. The consumer that owns `invokedFromBin` (fallback). This keeps bare `npx <pkg> claude-sync` working from arbitrary cwds — including from inside another consumer's directory where option 2 would have picked a different package.
|
|
55
|
+
4. The sole discovered consumer, if exactly one exists.
|
|
56
|
+
5. Otherwise an error asking for `--package=<name>` or `--all`.
|
|
57
|
+
|
|
58
|
+
Passing `invokedFromBin: import.meta.url` remains the mechanism for the fallback case. Omit it in slats's own global bin so it behaves as a cross-consumer dispatcher.
|
|
59
|
+
|
|
60
|
+
Remember `chmod +x bin/claude-sync.mjs` (or rely on `files` entry to ship executable bit via npm).
|
|
61
|
+
|
|
62
|
+
## 3. `scripts/build-hashes.mjs` (one line import)
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
#!/usr/bin/env node
|
|
66
|
+
import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const { outPath, fileCount } = await buildHashes();
|
|
70
|
+
console.log(`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('❌ buildHashes failed:', err?.message ?? err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This reads the current `package.json` + its `claude.assetPath`, hashes every file under the asset root (ignoring `.omc/**`, `*.log`, `.DS_Store`), and writes `dist/claude-hashes.json`.
|
|
78
|
+
|
|
79
|
+
## 4. Isolation guardrails (optional but recommended)
|
|
80
|
+
|
|
81
|
+
In a `.dependency-cruiser.cjs`:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
{
|
|
85
|
+
name: 'src-no-bin',
|
|
86
|
+
severity: 'error',
|
|
87
|
+
from: { path: '^src/' },
|
|
88
|
+
to: { path: '^bin/' },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'src-no-docs',
|
|
92
|
+
severity: 'error',
|
|
93
|
+
from: { path: '^src/' },
|
|
94
|
+
to: { path: '^docs/' },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'src-no-claude-assets-sync',
|
|
98
|
+
severity: 'error',
|
|
99
|
+
from: { path: '^src/' },
|
|
100
|
+
to: { path: 'node_modules/@slats/claude-assets-sync' },
|
|
101
|
+
},
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Plus `"sideEffects": false` in `package.json`. The guardrails ensure the CLI engine never leaks into consumer runtime bundles.
|
|
105
|
+
|
|
106
|
+
## 5. End-user invocations
|
|
107
|
+
|
|
108
|
+
| Install topology | Working invocations |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Consumer is a **direct dep** of the user's project | `npx claude-sync --scope=user` *(bare)* |
|
|
111
|
+
| Always works (preferred in docs) | `npx -p @your-scope/your-package claude-sync --scope=user` |
|
|
112
|
+
| Consumer is a **transitive dep** | `npx -p @your-scope/your-package claude-sync --scope=user` |
|
|
113
|
+
| User has no consumer installed | `npx @slats/claude-assets-sync --package=@your-scope/your-package --scope=user` |
|
|
114
|
+
| Multiple consumers discovered | `npx claude-sync --package=@your-scope/your-package` *or* `npx claude-sync --all` |
|
|
115
|
+
|
|
116
|
+
### Scope resolution (project)
|
|
117
|
+
|
|
118
|
+
For `--scope=project`, the target `.claude` directory is resolved by walking up from `process.cwd()` and reusing the first existing `.claude` directory found. Only if no ancestor owns a `.claude` does the CLI fall back to `process.cwd()/.claude`.
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
workspace/
|
|
122
|
+
.claude/ ← reused target (auto-located)
|
|
123
|
+
packages/
|
|
124
|
+
@your-scope/your-package/ ← cd here and run claude-sync
|
|
125
|
+
bin/claude-sync.mjs
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Running `yarn claude-sync --scope=project` from `packages/@your-scope/your-package/` injects into `workspace/.claude`, not `packages/@your-scope/your-package/.claude`. The CLI logs `(auto-located)` in its resolution line when this happens.
|
|
129
|
+
|
|
130
|
+
## 6. Authoring `docs/claude/`
|
|
131
|
+
|
|
132
|
+
Any file tree works, but the recommended layout is:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
docs/claude/
|
|
136
|
+
├── skills/
|
|
137
|
+
│ └── <skill-name>/
|
|
138
|
+
│ ├── SKILL.md
|
|
139
|
+
│ └── knowledge/...
|
|
140
|
+
├── rules/...
|
|
141
|
+
└── commands/...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The hash manifest tracks every file under `docs/claude/` relative to the asset root. On inject, the CLI copies skills/rules/commands into the matching subtree under `.claude/`.
|
|
145
|
+
|
|
146
|
+
## 7. Verification checklist
|
|
147
|
+
|
|
148
|
+
- [ ] `yarn build` succeeds and emits `dist/claude-hashes.json` alongside the rest of `dist/`.
|
|
149
|
+
- [ ] `node bin/claude-sync.mjs --help` prints the `claude-sync` subcommand tree.
|
|
150
|
+
- [ ] `node bin/claude-sync.mjs list --json` emits an entry for your package with `hashesPresent: true`.
|
|
151
|
+
- [ ] `node bin/claude-sync.mjs --scope=project --dry-run --package=@your-scope/your-package` emits a copy plan.
|
|
152
|
+
- [ ] `yarn depcheck` (or whatever your dep-cruiser invocation is named) reports zero new violations.
|
|
153
|
+
- [ ] Consumer bundler tree-shakes away CLI code (verify by greping the built bundle: should contain zero references to `@slats/claude-assets-sync`).
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slats/claude-assets-sync",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared CLI engine that lets consumer packages inject their Claude docs (skills, rules, commands) into a user's .claude directory via a thin bin/inject-docs wrapper.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
|
8
8
|
"cli",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"inject",
|
|
10
|
+
"docs",
|
|
11
11
|
"skills",
|
|
12
12
|
"assets"
|
|
13
13
|
],
|
|
@@ -29,23 +29,34 @@
|
|
|
29
29
|
"source": "./src/index.ts",
|
|
30
30
|
"import": "./dist/index.mjs",
|
|
31
31
|
"require": "./dist/index.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./buildHashes": {
|
|
34
|
+
"import": "./scripts/buildHashes.mjs"
|
|
32
35
|
}
|
|
33
36
|
},
|
|
34
37
|
"main": "dist/index.cjs",
|
|
35
38
|
"module": "dist/index.mjs",
|
|
36
39
|
"types": "dist/index.d.ts",
|
|
37
|
-
"bin":
|
|
40
|
+
"bin": {
|
|
41
|
+
"claude-build-hashes": "./scripts/claude-build-hashes.mjs",
|
|
42
|
+
"claude-sync": "./bin/claude-sync.mjs"
|
|
43
|
+
},
|
|
38
44
|
"files": [
|
|
39
45
|
"dist",
|
|
46
|
+
"docs",
|
|
47
|
+
"bin",
|
|
48
|
+
"scripts",
|
|
40
49
|
"README.md"
|
|
41
50
|
],
|
|
42
51
|
"scripts": {
|
|
43
|
-
"build": "node scripts/inject-version.js && rollup -c && yarn build:types",
|
|
52
|
+
"build": "node scripts/inject-version.js && rollup -c && yarn build:types && yarn build:hashes",
|
|
53
|
+
"build:hashes": "node scripts/build-hashes.mjs",
|
|
44
54
|
"build:publish:npm": "yarn build && yarn publish:npm",
|
|
45
55
|
"build:types": "node ../../aileron/script/build/buildTypes.mjs",
|
|
46
|
-
"dev": "node scripts/inject-version.js && tsx src/
|
|
56
|
+
"dev": "node scripts/inject-version.js && tsx src/main.ts",
|
|
47
57
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
48
58
|
"lint": "eslint \"src/**/*.ts\"",
|
|
59
|
+
"prepublishOnly": "yarn build",
|
|
49
60
|
"publish:npm": "yarn npm publish --access public",
|
|
50
61
|
"test": "vitest",
|
|
51
62
|
"version:major": "yarn version major",
|
|
@@ -53,17 +64,14 @@
|
|
|
53
64
|
"version:patch": "yarn version patch"
|
|
54
65
|
},
|
|
55
66
|
"dependencies": {
|
|
56
|
-
"@
|
|
67
|
+
"@inquirer/prompts": "^8.4.2",
|
|
57
68
|
"commander": "^12.1.0",
|
|
58
|
-
"
|
|
59
|
-
"ink-select-input": "^6.2.0",
|
|
60
|
-
"ink-spinner": "^5.0.0",
|
|
61
|
-
"picocolors": "^1.1.1",
|
|
62
|
-
"react": "^19.1.1",
|
|
63
|
-
"react-dom": "^19.1.1"
|
|
69
|
+
"picocolors": "^1.1.1"
|
|
64
70
|
},
|
|
65
71
|
"devDependencies": {
|
|
66
|
-
"@
|
|
67
|
-
|
|
72
|
+
"@inquirer/testing": "^3.3.5"
|
|
73
|
+
},
|
|
74
|
+
"claude": {
|
|
75
|
+
"assetPath": "docs/claude"
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Thin wrapper — parses this package's package.json and delegates to
|
|
3
|
+
// @slats/claude-assets-sync/buildHashes. The `claude.assetPath` convention
|
|
4
|
+
// lives here, in the consumer; the library is generic.
|
|
5
|
+
import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
import { dirname, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
|
+
const pkg = JSON.parse(
|
|
12
|
+
await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
if (typeof pkg.claude?.assetPath === 'string') {
|
|
16
|
+
try {
|
|
17
|
+
const { outPath, fileCount } = await buildHashes({
|
|
18
|
+
packageRoot,
|
|
19
|
+
packageName: pkg.name,
|
|
20
|
+
packageVersion: pkg.version,
|
|
21
|
+
assetPath: pkg.claude.assetPath,
|
|
22
|
+
});
|
|
23
|
+
console.log(
|
|
24
|
+
`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`,
|
|
25
|
+
);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error('❌ build-hashes failed:', err?.message ?? err);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface BuildHashesOptions {
|
|
2
|
+
packageRoot?: string;
|
|
3
|
+
packageName?: string;
|
|
4
|
+
packageVersion?: string;
|
|
5
|
+
assetPathRel?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BuildHashesResult {
|
|
9
|
+
outPath: string;
|
|
10
|
+
fileCount: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildHashes(
|
|
14
|
+
opts?: BuildHashesOptions,
|
|
15
|
+
): Promise<BuildHashesResult>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Library + importable implementation for the hash manifest builder.
|
|
2
|
+
//
|
|
3
|
+
// Self-executing CLI behavior lives in `./claude-build-hashes.mjs` so this
|
|
4
|
+
// file stays free of top-level await and can be bundled into CJS/ESM outputs
|
|
5
|
+
// via Rollup without format errors.
|
|
6
|
+
//
|
|
7
|
+
// Exported for:
|
|
8
|
+
// - Consumer packages: `import { buildHashes } from '@slats/claude-assets-sync/buildHashes'`
|
|
9
|
+
// - Standalone bin: `./claude-build-hashes.mjs`
|
|
10
|
+
//
|
|
11
|
+
// The caller owns all package metadata. This function does not read
|
|
12
|
+
// package.json — consumers parse their own manifest and pass a ready-made
|
|
13
|
+
// set of values so the library stays free of field-shape assumptions.
|
|
14
|
+
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const MANIFEST_FILENAME = 'claude-hashes.json';
|
|
20
|
+
const NOISE = [/(^|\/)\.omc(\/|$)/, /(^|\/)\.DS_Store$/, /\.log$/];
|
|
21
|
+
|
|
22
|
+
const toPosix = (p) => (sep === '/' ? p : p.split(sep).join('/'));
|
|
23
|
+
|
|
24
|
+
async function* walk(root) {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err.code === 'ENOENT') return;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const abs = join(root, entry.name);
|
|
34
|
+
if (entry.isDirectory()) yield* walk(abs);
|
|
35
|
+
else if (entry.isFile()) yield abs;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function buildHashes(opts) {
|
|
40
|
+
if (
|
|
41
|
+
!opts ||
|
|
42
|
+
typeof opts.packageRoot !== 'string' ||
|
|
43
|
+
typeof opts.packageName !== 'string' ||
|
|
44
|
+
typeof opts.packageVersion !== 'string' ||
|
|
45
|
+
typeof opts.assetPath !== 'string'
|
|
46
|
+
) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'buildHashes requires { packageRoot, packageName, packageVersion, assetPath }.',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const { packageRoot, packageName, packageVersion, assetPath } = opts;
|
|
52
|
+
if (!isAbsolute(packageRoot)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`packageRoot must be an absolute path; received: ${packageRoot}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const assetRoot = resolve(packageRoot, assetPath);
|
|
58
|
+
const files = {};
|
|
59
|
+
for await (const abs of walk(assetRoot)) {
|
|
60
|
+
const rel = toPosix(relative(assetRoot, abs));
|
|
61
|
+
if (NOISE.some((re) => re.test(rel))) continue;
|
|
62
|
+
const buf = await readFile(abs);
|
|
63
|
+
files[rel] = createHash('sha256').update(buf).digest('hex');
|
|
64
|
+
}
|
|
65
|
+
const sorted = Object.fromEntries(
|
|
66
|
+
Object.entries(files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
67
|
+
);
|
|
68
|
+
const manifest = {
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
package: { name: packageName, version: packageVersion },
|
|
71
|
+
generatedAt: new Date().toISOString(),
|
|
72
|
+
algorithm: 'sha256',
|
|
73
|
+
assetRoot: assetPath,
|
|
74
|
+
files: sorted,
|
|
75
|
+
previousVersions: {},
|
|
76
|
+
};
|
|
77
|
+
const distDir = resolve(packageRoot, 'dist');
|
|
78
|
+
await mkdir(distDir, { recursive: true });
|
|
79
|
+
const outPath = join(distDir, MANIFEST_FILENAME);
|
|
80
|
+
await writeFile(outPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
81
|
+
return { outPath, fileCount: Object.keys(sorted).length };
|
|
82
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Standalone CLI for `claude-build-hashes`.
|
|
3
|
+
//
|
|
4
|
+
// Reads the consumer's package.json at process.cwd() to extract the asset
|
|
5
|
+
// path, then delegates to `buildHashes`. Consumers who want a different
|
|
6
|
+
// manifest layout can skip this bin and ship a one-line
|
|
7
|
+
// `scripts/build-hashes.mjs` that calls `buildHashes` directly with their
|
|
8
|
+
// own parsed values.
|
|
9
|
+
//
|
|
10
|
+
// Convention: `pkg.claude?.assetPath` with a fallback of `'claude'`.
|
|
11
|
+
// This convention is purely consumer-side — the library itself enforces
|
|
12
|
+
// nothing about package.json shape.
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import { resolve } from 'node:path';
|
|
15
|
+
|
|
16
|
+
import { buildHashes } from './buildHashes.mjs';
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const packageRoot = process.cwd();
|
|
20
|
+
const pkg = JSON.parse(
|
|
21
|
+
await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
|
|
22
|
+
);
|
|
23
|
+
if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`${packageRoot}/package.json must define "name" and "version".`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const assetPath =
|
|
29
|
+
typeof pkg.claude?.assetPath === 'string' && pkg.claude.assetPath.length > 0
|
|
30
|
+
? pkg.claude.assetPath
|
|
31
|
+
: 'claude';
|
|
32
|
+
const { outPath, fileCount } = await buildHashes({
|
|
33
|
+
packageRoot,
|
|
34
|
+
packageName: pkg.name,
|
|
35
|
+
packageVersion: pkg.version,
|
|
36
|
+
assetPath,
|
|
37
|
+
});
|
|
38
|
+
console.log(`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('❌ buildHashes failed:', err?.message ?? err);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|