@slats/claude-assets-sync 0.3.0 → 0.3.1
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 +9 -9
- package/dist/claude-hashes.json +7 -7
- package/dist/commands/runCli/runCli.mjs +7 -2
- package/dist/commands/runCli/type.d.ts +1 -0
- package/dist/commands/runCli/utils/renderOrFallback.d.ts +6 -0
- package/dist/commands/runCli/utils/renderOrFallback.mjs +12 -0
- package/dist/commands/runCli/utils/renderPlain.d.ts +11 -0
- package/dist/commands/runCli/utils/renderPlain.mjs +89 -0
- package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +9 -1
- package/dist/commands/runCli/utils/resolveScopeFlag.mjs +5 -11
- package/dist/commands/runCli/utils/toConsumerPackages.d.ts +9 -0
- package/dist/commands/runCli/utils/toConsumerPackages.mjs +26 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/injectDocs/index.d.ts +3 -2
- package/dist/core/injectDocs/type.d.ts +0 -19
- package/dist/core/scope/index.d.ts +1 -1
- package/dist/core/scope/scope.d.ts +0 -1
- package/dist/core/scope/scope.mjs +1 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/ui/InjectApp/InjectApp.d.ts +2 -0
- package/dist/ui/InjectApp/InjectApp.mjs +82 -0
- package/dist/ui/InjectApp/index.d.ts +2 -0
- package/dist/ui/InjectApp/utils/eventSelectors.d.ts +5 -0
- package/dist/ui/InjectApp/utils/eventSelectors.mjs +24 -0
- package/dist/ui/InjectApp/utils/phaseReducer.d.ts +2 -0
- package/dist/ui/InjectApp/utils/phaseReducer.mjs +130 -0
- package/dist/ui/InjectApp/utils/renderInjectApp.d.ts +2 -0
- package/dist/ui/InjectApp/utils/renderInjectApp.mjs +19 -0
- package/dist/ui/InjectApp/utils/type.d.ts +5 -0
- package/dist/ui/components/ActionRow.d.ts +7 -0
- package/dist/ui/components/ActionRow.mjs +45 -0
- package/dist/ui/components/Banner.d.ts +7 -0
- package/dist/ui/components/Banner.mjs +9 -0
- package/dist/ui/components/ConfirmForce.d.ts +8 -0
- package/dist/ui/components/ConfirmForce.mjs +35 -0
- package/dist/ui/components/ErrorPanel.d.ts +6 -0
- package/dist/ui/components/ErrorPanel.mjs +14 -0
- package/dist/ui/components/Footer.d.ts +8 -0
- package/dist/ui/components/Footer.mjs +27 -0
- package/dist/ui/components/PlanTable.d.ts +8 -0
- package/dist/ui/components/PlanTable.mjs +15 -0
- package/dist/ui/components/ProgressBar.d.ts +10 -0
- package/dist/ui/components/ProgressBar.mjs +28 -0
- package/dist/ui/components/ScopePicker.d.ts +7 -0
- package/dist/ui/components/ScopePicker.mjs +26 -0
- package/dist/ui/components/Spinner.d.ts +8 -0
- package/dist/ui/components/Spinner.mjs +10 -0
- package/dist/ui/components/StatusBadge.d.ts +8 -0
- package/dist/ui/components/StepTracker.d.ts +9 -0
- package/dist/ui/components/StepTracker.mjs +43 -0
- package/dist/ui/components/Summary.d.ts +9 -0
- package/dist/ui/components/Summary.mjs +30 -0
- package/dist/ui/components/TargetCard.d.ts +11 -0
- package/dist/ui/components/TargetCard.mjs +29 -0
- package/dist/ui/hooks/useApplyStep.d.ts +12 -0
- package/dist/ui/hooks/useApplyStep.mjs +30 -0
- package/dist/ui/hooks/useExitApp.d.ts +8 -0
- package/dist/ui/hooks/useExitApp.mjs +19 -0
- package/dist/ui/hooks/useForceConfirmStep.d.ts +9 -0
- package/dist/ui/hooks/useForceConfirmStep.mjs +24 -0
- package/dist/ui/hooks/useInjectSession.d.ts +10 -0
- package/dist/ui/hooks/useInjectSession.mjs +63 -0
- package/dist/ui/hooks/useInterval.d.ts +1 -0
- package/dist/ui/hooks/usePhase.d.ts +2 -0
- package/dist/ui/hooks/usePhase.mjs +9 -0
- package/dist/ui/hooks/usePlanStep.d.ts +13 -0
- package/dist/ui/hooks/usePlanStep.mjs +94 -0
- package/dist/ui/hooks/useResolveStep.d.ts +18 -0
- package/dist/ui/hooks/useResolveStep.mjs +21 -0
- package/dist/ui/hooks/useTerminalWidth.d.ts +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.mjs +16 -0
- package/dist/ui/theme/colors.d.ts +12 -0
- package/dist/ui/theme/colors.mjs +9 -0
- package/dist/ui/theme/icons.d.ts +29 -0
- package/dist/ui/theme/icons.mjs +17 -0
- package/dist/ui/theme/layout.d.ts +20 -0
- package/dist/ui/theme/layout.mjs +9 -0
- package/dist/ui/types/event.d.ts +45 -0
- package/dist/ui/types/index.d.ts +4 -0
- package/dist/ui/types/phase.d.ts +44 -0
- package/dist/ui/types/render.d.ts +6 -0
- package/dist/ui/types/target.d.ts +25 -0
- 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 +1 -1
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +4 -12
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +17 -14
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +18 -13
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +4 -4
- package/docs/consumer-integration.md +9 -8
- package/package.json +12 -7
- package/scripts/dev-ui-fixtures.ts +288 -0
- package/scripts/dev-ui.tsx +289 -0
- package/dist/commands/runCli/runCli.cjs +0 -53
- package/dist/commands/runCli/utils/classifyTarget.cjs +0 -48
- package/dist/commands/runCli/utils/injectOne.cjs +0 -47
- package/dist/commands/runCli/utils/injectOne.d.ts +0 -3
- package/dist/commands/runCli/utils/injectOne.mjs +0 -45
- package/dist/commands/runCli/utils/resolvePackage.cjs +0 -77
- package/dist/commands/runCli/utils/resolveScopeAlias.cjs +0 -69
- package/dist/commands/runCli/utils/resolveScopeFlag.cjs +0 -28
- package/dist/commands/runCli/utils/resolveTargets.cjs +0 -40
- package/dist/commands/runCli/utils/runInject.cjs +0 -52
- package/dist/commands/runCli/utils/runInject.d.ts +0 -3
- package/dist/commands/runCli/utils/runInject.mjs +0 -50
- package/dist/core/buildPlan/buildPlan.cjs +0 -42
- package/dist/core/buildPlan/utils/toPosix.cjs +0 -9
- package/dist/core/buildPlan/utils/walkFiles.cjs +0 -25
- package/dist/core/hash/hash.cjs +0 -30
- package/dist/core/hashManifest/hashManifest.cjs +0 -27
- package/dist/core/injectDocs/injectDocs.cjs +0 -43
- package/dist/core/injectDocs/injectDocs.d.ts +0 -2
- package/dist/core/injectDocs/injectDocs.mjs +0 -41
- package/dist/core/injectDocs/utils/applyAction.cjs +0 -21
- package/dist/core/injectDocs/utils/emitCiForceList.cjs +0 -10
- package/dist/core/injectDocs/utils/emitCiForceList.d.ts +0 -2
- package/dist/core/injectDocs/utils/emitCiForceList.mjs +0 -8
- package/dist/core/injectDocs/utils/printPlan.cjs +0 -20
- package/dist/core/injectDocs/utils/printPlan.d.ts +0 -2
- package/dist/core/injectDocs/utils/printPlan.mjs +0 -18
- package/dist/core/injectDocs/utils/summarize.cjs +0 -27
- package/dist/core/scope/scope.cjs +0 -46
- package/dist/core/scope/utils/isDirectory.cjs +0 -14
- package/dist/index.cjs +0 -20
- package/dist/prompts/confirmForce.cjs +0 -27
- package/dist/prompts/confirmForce.d.ts +0 -1
- package/dist/prompts/confirmForce.mjs +0 -25
- package/dist/prompts/index.d.ts +0 -2
- package/dist/prompts/selectScope.cjs +0 -30
- package/dist/prompts/selectScope.d.ts +0 -2
- package/dist/prompts/selectScope.mjs +0 -28
- package/dist/utils/asyncPool.cjs +0 -26
- package/dist/utils/heartbeat.cjs +0 -25
- package/dist/utils/heartbeat.d.ts +0 -16
- package/dist/utils/heartbeat.mjs +0 -23
- package/dist/utils/logger.cjs +0 -74
- package/dist/utils/version.cjs +0 -5
package/README.md
CHANGED
|
@@ -27,12 +27,10 @@ claude-build-hashes
|
|
|
27
27
|
|
|
28
28
|
### End-user invocation
|
|
29
29
|
|
|
30
|
+
The engine is not shipped as a runtime dependency of consumers. Always invoke via `npx -p @slats/claude-assets-sync ...`; the package manager fetches and caches the engine on demand.
|
|
31
|
+
|
|
30
32
|
```bash
|
|
31
|
-
# universal — every PM (pnpm strict / yarn-berry PnP included)
|
|
32
33
|
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
34
|
```
|
|
37
35
|
|
|
38
36
|
| Flag | Meaning |
|
|
@@ -59,7 +57,7 @@ For `--scope=project` the target `.claude` directory is resolved by walking up f
|
|
|
59
57
|
"build": "… && yarn build:hashes",
|
|
60
58
|
"build:hashes": "claude-build-hashes"
|
|
61
59
|
},
|
|
62
|
-
"
|
|
60
|
+
"devDependencies": {
|
|
63
61
|
"@slats/claude-assets-sync": "workspace:^"
|
|
64
62
|
},
|
|
65
63
|
"files": ["dist", "docs", "README.md"],
|
|
@@ -67,7 +65,7 @@ For `--scope=project` the target `.claude` directory is resolved by walking up f
|
|
|
67
65
|
}
|
|
68
66
|
```
|
|
69
67
|
|
|
70
|
-
- `@slats/claude-assets-sync` MUST be in `
|
|
68
|
+
- `@slats/claude-assets-sync` MUST be in `devDependencies` — the engine is a CLI-only tool and must not leak into end-user production installs. See Rationale below.
|
|
71
69
|
- Do **not** add any `bin` field. The engine is the sole CLI surface; per-consumer bins would collide under `node_modules/.bin/`.
|
|
72
70
|
- Do **not** expose `./bin/*` or `./docs/*` in `exports`. That would let consumer bundlers pull CLI code or the asset tree into app bundles.
|
|
73
71
|
- Do **not** create a `bin/` or `scripts/` directory in the consumer.
|
|
@@ -82,10 +80,12 @@ yarn build
|
|
|
82
80
|
|
|
83
81
|
Ship the resulting `dist/` (including `claude-hashes.json`) alongside `docs/` when you publish.
|
|
84
82
|
|
|
85
|
-
### Rationale: `
|
|
83
|
+
### Rationale: `devDependencies`, not `dependencies`
|
|
86
84
|
|
|
87
|
-
- The
|
|
88
|
-
-
|
|
85
|
+
- The engine is used at two moments only: (1) the consumer's own build, where `claude-build-hashes` produces `dist/claude-hashes.json`, and (2) the end user's one-off `inject-claude-settings` invocation. Neither is runtime behaviour of the consumer library.
|
|
86
|
+
- Putting the engine in `dependencies` would force every downstream installer of the consumer to pull `commander`, `@inquirer/prompts`, and their transitive trees into their production `node_modules` — dead weight for anyone who never sets up Claude Code assets.
|
|
87
|
+
- The workspace build chain still resolves `.bin/claude-build-hashes` from `devDependencies` at `yarn install` time; yarn workspaces link devDeps and deps identically for workspace-local builds.
|
|
88
|
+
- End users never rely on a hoisted `inject-claude-settings` bin. The canonical invocation is `npx -p @slats/claude-assets-sync inject-claude-settings --package=<THIS>`, which fetches the engine on demand and caches it.
|
|
89
89
|
- Bundle isolation is enforced by the import graph (`src/**` in the consumer never references the engine), not by dependency-type.
|
|
90
90
|
|
|
91
91
|
## Authoring `docs/claude/`
|
package/dist/claude-hashes.json
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@slats/claude-assets-sync",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.1"
|
|
6
6
|
},
|
|
7
|
-
"generatedAt": "2026-04-
|
|
7
|
+
"generatedAt": "2026-04-24T16:37:59.339Z",
|
|
8
8
|
"algorithm": "sha256",
|
|
9
9
|
"assetRoot": "docs/claude",
|
|
10
10
|
"files": {
|
|
11
|
-
"skills/claude-docs-asset-wiring/SKILL.md": "
|
|
12
|
-
"skills/claude-docs-asset-wiring/knowledge/claude-md-template.md": "
|
|
11
|
+
"skills/claude-docs-asset-wiring/SKILL.md": "4864a5a1aba0813fb64ad19111f3db4369df6cc025e485d03d17cdc9d245bce5",
|
|
12
|
+
"skills/claude-docs-asset-wiring/knowledge/claude-md-template.md": "b0274c5a37076a7e80bd7beccd739b10cc91ca5395fe425be25d9129206c684a",
|
|
13
13
|
"skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md": "18e994bd16242f72a4dc6cfa508493f4b63853a7a16c4910c7b7abd0368bd103",
|
|
14
|
-
"skills/claude-docs-asset-wiring/knowledge/gotchas.md": "
|
|
15
|
-
"skills/claude-docs-asset-wiring/knowledge/package-json-patches.md": "
|
|
16
|
-
"skills/claude-docs-asset-wiring/knowledge/reference-files.md": "
|
|
14
|
+
"skills/claude-docs-asset-wiring/knowledge/gotchas.md": "70a6c75ea09816ec457615c060e6a87197bc5ed332a377452b3cacd974b44eee",
|
|
15
|
+
"skills/claude-docs-asset-wiring/knowledge/package-json-patches.md": "063469932191843a2f7f9156a513717947bcad584c4084a0c8300897683885e7",
|
|
16
|
+
"skills/claude-docs-asset-wiring/knowledge/reference-files.md": "1b5f76d262f47835c4d0150a48fa25dfc644609fd4ecad260273a4cab57e41a2",
|
|
17
17
|
"skills/claude-docs-asset-wiring/knowledge/smoke-tests.md": "c90fc82d46bf4b55d794c2ce463b4c1e199d13d6b1161028486fb4bbbd6a60fe"
|
|
18
18
|
},
|
|
19
19
|
"previousVersions": {}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { logger } from '../../utils/logger.mjs';
|
|
3
3
|
import { VERSION } from '../../utils/version.mjs';
|
|
4
|
+
import { renderOrFallback } from './utils/renderOrFallback.mjs';
|
|
4
5
|
import { resolveTargets } from './utils/resolveTargets.mjs';
|
|
5
|
-
import {
|
|
6
|
+
import { toConsumerPackages } from './utils/toConsumerPackages.mjs';
|
|
6
7
|
|
|
7
8
|
async function runCli(argv = process.argv) {
|
|
8
9
|
const cmd = new Command();
|
|
@@ -15,6 +16,7 @@ async function runCli(argv = process.argv) {
|
|
|
15
16
|
.option('--dry-run', 'Preview without writing', false)
|
|
16
17
|
.option('--force', 'Overwrite user modifications', false)
|
|
17
18
|
.option('--root <path>', 'Override scope resolution cwd (default: cwd)')
|
|
19
|
+
.option('--json', 'Emit structured JSON output (forces non-interactive legacy logger path)', false)
|
|
18
20
|
.action(async (flags) => {
|
|
19
21
|
const targets = flags.package ?? [];
|
|
20
22
|
if (targets.length === 0) {
|
|
@@ -27,7 +29,10 @@ async function runCli(argv = process.argv) {
|
|
|
27
29
|
logger.warn(`no packages resolved from --package target(s): ${targets.join(', ')}`);
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
|
-
await
|
|
32
|
+
const consumerPackages = await toConsumerPackages(metadataList);
|
|
33
|
+
const exitCode = await renderOrFallback(consumerPackages, flags, originCwd);
|
|
34
|
+
if (exitCode !== 0)
|
|
35
|
+
process.exit(exitCode);
|
|
31
36
|
});
|
|
32
37
|
try {
|
|
33
38
|
await cmd.parseAsync([...argv]);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConsumerPackage, DefaultFlags } from '../type.js';
|
|
2
|
+
interface RenderEnv {
|
|
3
|
+
readonly isTTY?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function renderOrFallback(targets: readonly ConsumerPackage[], flags: DefaultFlags, originCwd: string, env?: RenderEnv): Promise<number>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { renderPlain } from './renderPlain.mjs';
|
|
2
|
+
|
|
3
|
+
async function renderOrFallback(targets, flags, originCwd, env = {}) {
|
|
4
|
+
const isTTY = env.isTTY ?? Boolean(process.stdout.isTTY);
|
|
5
|
+
if (flags.json || !isTTY) {
|
|
6
|
+
return renderPlain(targets, flags, originCwd);
|
|
7
|
+
}
|
|
8
|
+
const ui = (await import('../../../ui/index.mjs'));
|
|
9
|
+
return ui.renderInjectApp({ targets, flags, originCwd });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { renderOrFallback };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConsumerPackage, DefaultFlags } from '../type.js';
|
|
2
|
+
/**
|
|
3
|
+
* Plain (picocolors) renderer for non-TTY / `--json` invocations.
|
|
4
|
+
*
|
|
5
|
+
* Composes the same `core/**` primitives that the Ink `useInjectSession`
|
|
6
|
+
* pipeline uses — no legacy `injectDocs` orchestrator in between.
|
|
7
|
+
* Missing `--scope` on non-TTY exits via `resolveScopeFlag` with code 2.
|
|
8
|
+
* Divergent/orphan files still respect `--force`: absence of `--force`
|
|
9
|
+
* returns 2; presence logs the list to stderr and proceeds.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderPlain(targets: readonly ConsumerPackage[], flags: DefaultFlags, originCwd: string): Promise<number>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import 'node:crypto';
|
|
2
|
+
import 'node:fs/promises';
|
|
3
|
+
import { readHashManifest, computeNamespacePrefixes } from '../../../core/hashManifest/hashManifest.mjs';
|
|
4
|
+
import { applyAction } from '../../../core/injectDocs/utils/applyAction.mjs';
|
|
5
|
+
import { summarize } from '../../../core/injectDocs/utils/summarize.mjs';
|
|
6
|
+
import { buildPlan } from '../../../core/buildPlan/buildPlan.mjs';
|
|
7
|
+
import { resolveScope } from '../../../core/scope/scope.mjs';
|
|
8
|
+
import { asyncPool } from '../../../utils/asyncPool.mjs';
|
|
9
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
10
|
+
import { resolveScopeFlag } from './resolveScopeFlag.mjs';
|
|
11
|
+
|
|
12
|
+
async function renderPlain(targets, flags, originCwd) {
|
|
13
|
+
if (targets.length === 0)
|
|
14
|
+
return 0;
|
|
15
|
+
const scope = resolveScopeFlag(flags.scope);
|
|
16
|
+
const fatalOnError = targets.length === 1;
|
|
17
|
+
let failureCount = 0;
|
|
18
|
+
for (const target of targets) {
|
|
19
|
+
if (!target.hashesPresent) {
|
|
20
|
+
logger.warn(`${target.name}: dist/claude-hashes.json missing — build the package (e.g. yarn build) to regenerate the hash manifest first.`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
logger.heading(`${target.name}@${target.version}`);
|
|
24
|
+
let exitCode;
|
|
25
|
+
try {
|
|
26
|
+
exitCode = await renderOneTarget(target, scope, flags, originCwd);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
30
|
+
logger.error(`${target.name}: ${msg}`);
|
|
31
|
+
exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
if (exitCode !== 0) {
|
|
34
|
+
if (fatalOnError)
|
|
35
|
+
return exitCode;
|
|
36
|
+
failureCount += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return failureCount > 0 ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
async function renderOneTarget(target, scope, flags, originCwd) {
|
|
42
|
+
const manifest = await readHashManifest(target.packageRoot);
|
|
43
|
+
const resolution = resolveScope(scope, originCwd);
|
|
44
|
+
const plan = await buildPlan({
|
|
45
|
+
sourceHashes: manifest.files,
|
|
46
|
+
targetRoot: resolution.targetRoot,
|
|
47
|
+
namespacePrefixes: computeNamespacePrefixes(manifest),
|
|
48
|
+
force: flags.force ?? false,
|
|
49
|
+
});
|
|
50
|
+
logger.info(`${target.name}@${target.version} → ${resolution.description}`);
|
|
51
|
+
printPlan(plan);
|
|
52
|
+
if (plan.requiresForce && !flags.force) {
|
|
53
|
+
logger.error('Re-run with --force to proceed, or inspect with --dry-run.');
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
if (flags.force &&
|
|
57
|
+
plan.actions.some((a) => a.kind === 'warn-diverged' || a.kind === 'warn-orphan')) {
|
|
58
|
+
emitForceList(plan);
|
|
59
|
+
}
|
|
60
|
+
if (flags.dryRun) {
|
|
61
|
+
logger.warn('[DRY RUN] No files will be created, overwritten, or deleted.');
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
await asyncPool(8, plan.actions, (action) => applyAction(action, target.assetRoot));
|
|
65
|
+
const report = summarize(plan, 0);
|
|
66
|
+
return report.exitCode;
|
|
67
|
+
}
|
|
68
|
+
function printPlan(plan) {
|
|
69
|
+
for (const action of plan.actions) {
|
|
70
|
+
if (action.kind === 'copy')
|
|
71
|
+
logger.file('create', action.relPath);
|
|
72
|
+
else if (action.kind === 'skip-uptodate')
|
|
73
|
+
logger.file('skip', `${action.relPath} (up-to-date)`);
|
|
74
|
+
else if (action.kind === 'warn-diverged')
|
|
75
|
+
logger.warn(`${action.relPath} — local differs from source (user edit or version change)`);
|
|
76
|
+
else if (action.kind === 'warn-orphan')
|
|
77
|
+
logger.warn(`${action.relPath} — present locally, absent in source`);
|
|
78
|
+
else if (action.kind === 'delete')
|
|
79
|
+
logger.file('update', `${action.relPath} (deleting)`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function emitForceList(plan) {
|
|
83
|
+
const divergent = plan.actions.filter((action) => action.kind === 'warn-diverged' || action.kind === 'warn-orphan');
|
|
84
|
+
process.stderr.write(`[claude-assets-sync] --force overwriting ${divergent.length} file(s) in non-TTY mode:\n`);
|
|
85
|
+
for (const action of divergent)
|
|
86
|
+
process.stderr.write(` ${action.relPath}\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { renderPlain };
|
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import { type Scope } from '../../../core/index.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Legacy (non-TTY / --json) scope resolver.
|
|
4
|
+
*
|
|
5
|
+
* The TTY Ink path owns its own scope picker via `ui/components/ScopePicker`.
|
|
6
|
+
* This helper runs only after `renderOrFallback` has chosen the legacy path,
|
|
7
|
+
* where prompting is not appropriate — either stdout is piped or the caller
|
|
8
|
+
* asked for structured `--json` output. Missing flag → exit 2.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveScopeFlag(flag: string | undefined): Scope;
|
|
@@ -2,12 +2,9 @@ import 'node:crypto';
|
|
|
2
2
|
import 'node:fs/promises';
|
|
3
3
|
import 'node:path';
|
|
4
4
|
import { logger } from '../../../utils/logger.mjs';
|
|
5
|
-
import { isValidScope
|
|
6
|
-
import { selectScopeAsync } from '../../../prompts/selectScope.mjs';
|
|
7
|
-
import '@inquirer/prompts';
|
|
8
|
-
import 'picocolors';
|
|
5
|
+
import { isValidScope } from '../../../core/scope/scope.mjs';
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
function resolveScopeFlag(flag) {
|
|
11
8
|
if (flag) {
|
|
12
9
|
if (!isValidScope(flag)) {
|
|
13
10
|
logger.error(`Invalid --scope: ${flag}. Expected user | project.`);
|
|
@@ -15,12 +12,9 @@ async function resolveScopeFlag(flag) {
|
|
|
15
12
|
}
|
|
16
13
|
return flag;
|
|
17
14
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
process.exit(2);
|
|
22
|
-
}
|
|
23
|
-
return selectScopeAsync();
|
|
15
|
+
logger.error('--scope is required in non-interactive environments.');
|
|
16
|
+
logger.error(' Pass --scope=user or --scope=project.');
|
|
17
|
+
process.exit(2);
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
export { resolveScopeFlag };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConsumerPackage } from '../type.js';
|
|
2
|
+
import type { ResolvedMetadata } from './resolvePackage.js';
|
|
3
|
+
/**
|
|
4
|
+
* Convert dispatcher `ResolvedMetadata` into runtime `ConsumerPackage`.
|
|
5
|
+
* Resolves the asset root against `packageRoot` and probes for
|
|
6
|
+
* `dist/claude-hashes.json` presence so both the Ink and legacy paths
|
|
7
|
+
* can treat the target uniformly.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toConsumerPackages(metadataList: readonly ResolvedMetadata[]): Promise<ConsumerPackage[]>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, resolve, join } from 'node:path';
|
|
3
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
4
|
+
|
|
5
|
+
async function toConsumerPackages(metadataList) {
|
|
6
|
+
const result = [];
|
|
7
|
+
for (const metadata of metadataList) {
|
|
8
|
+
if (!isAbsolute(metadata.packageRoot)) {
|
|
9
|
+
logger.error(`packageRoot must be an absolute path; received: ${metadata.packageRoot}`);
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
const assetRoot = resolve(metadata.packageRoot, metadata.assetPath);
|
|
13
|
+
const hashesPath = join(metadata.packageRoot, 'dist', 'claude-hashes.json');
|
|
14
|
+
const hashesPresent = await stat(hashesPath).then(() => true, () => false);
|
|
15
|
+
result.push({
|
|
16
|
+
name: metadata.packageName,
|
|
17
|
+
version: metadata.packageVersion,
|
|
18
|
+
packageRoot: metadata.packageRoot,
|
|
19
|
+
assetRoot,
|
|
20
|
+
hashesPresent,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { toConsumerPackages };
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { hashContent, hashEquals, hashFile, type Sha256Hex, } from './hash/index.js';
|
|
2
2
|
export { HASH_MANIFEST_FILENAME, computeNamespacePrefixes, readHashManifest, type HashManifest, } from './hashManifest/index.js';
|
|
3
|
-
export {
|
|
3
|
+
export { applyAction, summarize, type InjectReport, } from './injectDocs/index.js';
|
|
4
4
|
export { buildPlan, type Action, type InjectPlan, type PlanInput, } from './buildPlan/index.js';
|
|
5
|
-
export { findNearestDotClaudeAncestor,
|
|
5
|
+
export { findNearestDotClaudeAncestor, isValidScope, resolveScope, type Scope, type ScopeResolution, } from './scope/index.js';
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
1
|
+
export { applyAction } from './utils/applyAction.js';
|
|
2
|
+
export { summarize } from './utils/summarize.js';
|
|
3
|
+
export type { InjectReport } from './type.js';
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
import type { InjectPlan } from '../buildPlan/index.js';
|
|
2
|
-
import type { Scope } from '../scope/index.js';
|
|
3
|
-
export interface InjectOptions {
|
|
4
|
-
packageName: string;
|
|
5
|
-
packageVersion: string;
|
|
6
|
-
packageRoot: string;
|
|
7
|
-
assetRoot: string;
|
|
8
|
-
scope: Scope;
|
|
9
|
-
dryRun: boolean;
|
|
10
|
-
force: boolean;
|
|
11
|
-
/**
|
|
12
|
-
* Origin directory used to resolve project/local scope targets. When set,
|
|
13
|
-
* `resolveScope` walks up from this path to find the nearest existing
|
|
14
|
-
* `.claude` ancestor. Defaults to `process.cwd()`.
|
|
15
|
-
*/
|
|
16
|
-
originCwd?: string;
|
|
17
|
-
/** Called AFTER plan is built but BEFORE apply. Return false to abort. */
|
|
18
|
-
confirmForce?: (plan: InjectPlan) => Promise<boolean>;
|
|
19
|
-
}
|
|
20
1
|
export interface InjectReport {
|
|
21
2
|
created: string[];
|
|
22
3
|
updated: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { findNearestDotClaudeAncestor,
|
|
1
|
+
export { findNearestDotClaudeAncestor, isValidScope, resolveScope, type Scope, type ScopeResolution, } from './scope.js';
|
|
@@ -13,4 +13,3 @@ export declare function isValidScope(value: unknown): value is Scope;
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function findNearestDotClaudeAncestor(start: string): string | null;
|
|
15
15
|
export declare function resolveScope(scope: Scope, cwd?: string): ScopeResolution;
|
|
16
|
-
export declare function isInteractive(): boolean;
|
|
@@ -34,8 +34,5 @@ function resolveScope(scope, cwd = process.cwd()) {
|
|
|
34
34
|
: `${base}/.claude (project)`,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
function isInteractive() {
|
|
38
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
39
|
-
}
|
|
40
37
|
|
|
41
|
-
export { findNearestDotClaudeAncestor,
|
|
38
|
+
export { findNearestDotClaudeAncestor, isValidScope, resolveScope };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { runCli } from './commands/index.js';
|
|
2
|
-
export { HASH_MANIFEST_FILENAME, computeNamespacePrefixes,
|
|
2
|
+
export { HASH_MANIFEST_FILENAME, computeNamespacePrefixes, isValidScope, readHashManifest, resolveScope, type HashManifest, type InjectReport, type Scope, type ScopeResolution, } from './core/index.js';
|
|
3
3
|
export type { AssetType } from './utils/types.js';
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,6 @@ export { runCli } from './commands/runCli/runCli.mjs';
|
|
|
2
2
|
import 'node:crypto';
|
|
3
3
|
import 'node:fs/promises';
|
|
4
4
|
export { HASH_MANIFEST_FILENAME, computeNamespacePrefixes, readHashManifest } from './core/hashManifest/hashManifest.mjs';
|
|
5
|
-
export { injectDocs } from './core/injectDocs/injectDocs.mjs';
|
|
6
5
|
import 'node:path';
|
|
7
|
-
|
|
6
|
+
import 'picocolors';
|
|
7
|
+
export { isValidScope, resolveScope } from './core/scope/scope.mjs';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { Banner } from '../components/Banner.mjs';
|
|
5
|
+
import { ConfirmForce } from '../components/ConfirmForce.mjs';
|
|
6
|
+
import { ErrorPanel } from '../components/ErrorPanel.mjs';
|
|
7
|
+
import { Footer } from '../components/Footer.mjs';
|
|
8
|
+
import { ProgressBar } from '../components/ProgressBar.mjs';
|
|
9
|
+
import { ScopePicker } from '../components/ScopePicker.mjs';
|
|
10
|
+
import { Spinner } from '../components/Spinner.mjs';
|
|
11
|
+
import { StepTracker } from '../components/StepTracker.mjs';
|
|
12
|
+
import { Summary } from '../components/Summary.mjs';
|
|
13
|
+
import { TargetCard } from '../components/TargetCard.mjs';
|
|
14
|
+
import { useExitApp } from '../hooks/useExitApp.mjs';
|
|
15
|
+
import { useInjectSession } from '../hooks/useInjectSession.mjs';
|
|
16
|
+
import { usePhase } from '../hooks/usePhase.mjs';
|
|
17
|
+
import { colors } from '../theme/colors.mjs';
|
|
18
|
+
import { icons } from '../theme/icons.mjs';
|
|
19
|
+
import { scopeLabel, etaSeconds } from './utils/eventSelectors.mjs';
|
|
20
|
+
|
|
21
|
+
const VERSION = '0.3.0';
|
|
22
|
+
function InjectApp(props) {
|
|
23
|
+
const { targets, flags, originCwd, onExit } = props;
|
|
24
|
+
const [phase, dispatch] = usePhase({ kind: 'resolving', targets });
|
|
25
|
+
useInjectSession({ targets, flags, originCwd, dispatch });
|
|
26
|
+
const handleExit = useCallback((code) => {
|
|
27
|
+
onExit(code);
|
|
28
|
+
}, [onExit]);
|
|
29
|
+
useExitApp({
|
|
30
|
+
enabled: phase.kind === 'summary',
|
|
31
|
+
exitCode: phase.kind === 'summary' ? phase.exitCode : 0,
|
|
32
|
+
onExit: handleExit,
|
|
33
|
+
delayMs: 100,
|
|
34
|
+
});
|
|
35
|
+
useExitApp({
|
|
36
|
+
enabled: phase.kind === 'error',
|
|
37
|
+
exitCode: 1,
|
|
38
|
+
onExit: handleExit,
|
|
39
|
+
delayMs: 100,
|
|
40
|
+
});
|
|
41
|
+
return (jsxs(Box, { flexDirection: "column", children: [jsx(Banner, { version: VERSION, scope: scopeLabel(phase) }), jsx(Box, { marginTop: 1, children: jsx(StepTracker, { phase: phase, targetCount: targets.length, scope: scopeLabel(phase) }) }), jsx(Box, { flexDirection: "column", marginTop: 1, children: renderPhaseBody(phase) }), jsx(Footer, { phase: phase, version: VERSION })] }));
|
|
42
|
+
}
|
|
43
|
+
function renderPhaseBody(phase) {
|
|
44
|
+
switch (phase.kind) {
|
|
45
|
+
case 'booting':
|
|
46
|
+
case 'resolving':
|
|
47
|
+
return jsx(Spinner, { label: "resolving targets\u2026" });
|
|
48
|
+
case 'scope-select':
|
|
49
|
+
return jsx(ScopePicker, { onSelect: phase.pending });
|
|
50
|
+
case 'planning':
|
|
51
|
+
return (jsxs(Box, { flexDirection: "column", children: [jsx(Spinner, { label: "building plans\u2026" }), jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [...phase.progress.values()].map((step) => (jsxs(Box, { children: [jsxs(Text, { color: step.status === 'done'
|
|
52
|
+
? colors.success
|
|
53
|
+
: step.status === 'running'
|
|
54
|
+
? colors.warn
|
|
55
|
+
: step.status === 'failed'
|
|
56
|
+
? colors.danger
|
|
57
|
+
: colors.muted, bold: step.status === 'running', children: [step.status === 'done'
|
|
58
|
+
? icons.check
|
|
59
|
+
: step.status === 'failed'
|
|
60
|
+
? icons.cross
|
|
61
|
+
: step.status === 'running'
|
|
62
|
+
? icons.bulletActive
|
|
63
|
+
: icons.bulletPending, ' '] }), jsx(Text, { children: step.packageName }), step.error ? (jsxs(Text, { color: colors.danger, dimColor: true, children: [' ', "(", step.error, ")"] })) : null] }, step.packageName))) })] }));
|
|
64
|
+
case 'diff-review':
|
|
65
|
+
return (jsxs(Box, { flexDirection: "column", children: [phase.plans.map((tp, idx) => (jsx(TargetCard, { target: tp.target, plan: tp.plan, expanded: true, highlighted: idx === phase.focusedIndex }, tp.target.name))), jsx(Box, { marginTop: 1, children: jsxs(Text, { color: colors.muted, dimColor: true, children: ["Applying ", phase.plans.length, " plan(s)\u2026"] }) })] }));
|
|
66
|
+
case 'force-confirm':
|
|
67
|
+
return (jsx(ConfirmForce, { warnings: phase.warnings, onAnswer: phase.pending }));
|
|
68
|
+
case 'applying': {
|
|
69
|
+
const eta = etaSeconds(phase.progress.startedAt, phase.progress.done, phase.progress.total);
|
|
70
|
+
return (jsxs(Box, { flexDirection: "column", children: [jsx(ProgressBar, { done: phase.progress.done, total: phase.progress.total, etaSeconds: eta, label: phase.progress.current }), jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: phase.plans.map((tp) => (jsx(TargetCard, { target: tp.target, plan: tp.plan, expanded: false }, tp.target.name))) })] }));
|
|
71
|
+
}
|
|
72
|
+
case 'summary':
|
|
73
|
+
return (jsxs(Box, { flexDirection: "column", children: [phase.plans.map((tp) => (jsx(TargetCard, { target: tp.target, plan: tp.plan, expanded: false }, tp.target.name))), jsx(Summary, { reports: phase.reports, exitCode: phase.exitCode, dryRun: phase.dryRun })] }));
|
|
74
|
+
case 'error':
|
|
75
|
+
return jsx(ErrorPanel, { error: phase.error });
|
|
76
|
+
default: {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { InjectApp };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ConsumerPackage } from '../../../commands/runCli/type.js';
|
|
2
|
+
import type { Phase } from '../../types/index.js';
|
|
3
|
+
export declare function scopeLabel(phase: Phase): string | undefined;
|
|
4
|
+
export declare function targetsOf(phase: Phase): readonly ConsumerPackage[];
|
|
5
|
+
export declare function etaSeconds(startedAt: number, done: number, total: number, now?: number): number | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function scopeLabel(phase) {
|
|
2
|
+
switch (phase.kind) {
|
|
3
|
+
case 'planning':
|
|
4
|
+
case 'diff-review':
|
|
5
|
+
case 'force-confirm':
|
|
6
|
+
case 'applying':
|
|
7
|
+
case 'summary':
|
|
8
|
+
return phase.scope;
|
|
9
|
+
default:
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function etaSeconds(startedAt, done, total, now = Date.now()) {
|
|
14
|
+
if (done === 0)
|
|
15
|
+
return undefined;
|
|
16
|
+
const elapsedMs = Math.max(now - startedAt, 1);
|
|
17
|
+
const rate = done / elapsedMs;
|
|
18
|
+
const remaining = total - done;
|
|
19
|
+
if (rate <= 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
return remaining / rate / 1000;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { etaSeconds, scopeLabel };
|