@slats/claude-assets-sync 0.3.1 → 0.3.2
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 +7 -3
- package/dist/claude-hashes.json +2 -2
- package/dist/commands/runCli/runCli.d.ts +4 -2
- package/dist/commands/runCli/utils/resolveScopeAlias.mjs +30 -33
- package/dist/ui/InjectApp/InjectApp.mjs +2 -2
- package/dist/ui/InjectApp/utils/phaseReducer.mjs +4 -1
- package/dist/ui/hooks/useExitApp.mjs +1 -1
- package/dist/ui/hooks/usePlanStep.mjs +2 -1
- package/dist/ui/index.mjs +1 -1
- package/dist/ui/types/target.d.ts +1 -1
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.mjs +1 -1
- package/package.json +1 -1
- package/scripts/dev-ui.tsx +1 -2
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ Engine + dispatcher CLI that lets any npm package ship its own Claude Code docs
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
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
|
|
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 each consumer's metadata, compares its hash manifest against the target `.claude/`, and copies only what is out of date.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`--package` accepts a scoped name (`@scope/pkg`), an unscoped name (`pkg`), or a **scope alias** (`@scope` with no slash) that fans out to every installed `node_modules/@scope/*` package declaring `claude.assetPath`. Single-target resolution uses `createRequire`; scope-alias enumeration walks ancestor `node_modules/@<scope>/` directories from `cwd` upward and is isolated to `runCli/utils/resolveScopeAlias.ts`.
|
|
10
10
|
|
|
11
11
|
No GitHub fetch, no `.sync-meta.json`, no migrations — the consumer's `dist/claude-hashes.json` is the single source of truth.
|
|
12
12
|
|
|
@@ -30,12 +30,16 @@ claude-build-hashes
|
|
|
30
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
31
|
|
|
32
32
|
```bash
|
|
33
|
+
# Single consumer:
|
|
33
34
|
npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=user
|
|
35
|
+
|
|
36
|
+
# Scope alias — every installed @winglet/* that declares claude.assetPath:
|
|
37
|
+
npx -p @slats/claude-assets-sync inject-claude-settings --package=@winglet --scope=user
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
| Flag | Meaning |
|
|
37
41
|
|---|---|
|
|
38
|
-
| `--package <name>` | **Required.**
|
|
42
|
+
| `--package <name>` | **Required.** Repeatable/comma-separable. Accepts `@scope/pkg`, `pkg`, or a scope alias `@scope` (fans out to every installed `node_modules/@scope/*` with `claude.assetPath`). |
|
|
39
43
|
| `--scope=user` | `~/.claude` (applies globally). |
|
|
40
44
|
| `--scope=project` | Nearest ancestor `.claude` directory, or `<cwd>/.claude` if none found. |
|
|
41
45
|
| `--dry-run` | Print the copy / skip / warn plan, no writes. |
|
package/dist/claude-hashes.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@slats/claude-assets-sync",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.2"
|
|
6
6
|
},
|
|
7
|
-
"generatedAt": "2026-04-
|
|
7
|
+
"generatedAt": "2026-04-24T17:08:31.012Z",
|
|
8
8
|
"algorithm": "sha256",
|
|
9
9
|
"assetRoot": "docs/claude",
|
|
10
10
|
"files": {
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The `inject-claude-settings` dispatcher parses `--package <name...>`
|
|
5
5
|
* from argv and classifies each value:
|
|
6
|
-
* - `@<scope>` — enumerate every
|
|
6
|
+
* - `@<scope>` — enumerate every installed `node_modules/@<scope>/*`
|
|
7
|
+
* package that declares `claude.assetPath`
|
|
7
8
|
* - `@<scope>/<name>` — one scoped package
|
|
8
9
|
* - `<name>` — one unscoped package
|
|
9
10
|
*
|
|
10
11
|
* Targets are resolved via Node module resolution (`resolvePackage`)
|
|
11
12
|
* except for scope aliases, which are the only path allowed to walk
|
|
12
|
-
*
|
|
13
|
+
* `node_modules` siblings — that exception is isolated to
|
|
14
|
+
* `resolveScopeAlias.ts`.
|
|
13
15
|
*/
|
|
14
16
|
export declare function runCli(argv?: readonly string[]): Promise<void>;
|
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { resolve, join, dirname } from 'node:path';
|
|
4
4
|
import { logger } from '../../../utils/logger.mjs';
|
|
5
5
|
import { resolvePackage } from './resolvePackage.mjs';
|
|
6
6
|
|
|
7
7
|
async function resolveScopeAlias(scope, rootCwd) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const expectedPrefix = `@${scope}/`;
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
const matchedNames = [];
|
|
11
|
+
let cur = resolve(rootCwd);
|
|
12
|
+
while (true) {
|
|
13
|
+
const scopeDir = join(cur, 'node_modules', `@${scope}`);
|
|
14
|
+
await collectScopeDir(scopeDir, expectedPrefix, seen, matchedNames);
|
|
15
|
+
const parent = dirname(cur);
|
|
16
|
+
if (parent === cur)
|
|
17
|
+
break;
|
|
18
|
+
cur = parent;
|
|
19
|
+
}
|
|
20
|
+
if (matchedNames.length === 0) {
|
|
21
|
+
logger.error(`scope alias "@${scope}" matched no installed packages in any ancestor "node_modules/@${scope}/" walking up from ${rootCwd}.`);
|
|
11
22
|
process.exit(2);
|
|
12
23
|
}
|
|
13
|
-
const
|
|
24
|
+
const resolved = [];
|
|
25
|
+
for (const name of matchedNames) {
|
|
26
|
+
const meta = await resolvePackage(name, { skipMissingAsset: true });
|
|
27
|
+
if (meta)
|
|
28
|
+
resolved.push(meta);
|
|
29
|
+
}
|
|
30
|
+
return resolved;
|
|
31
|
+
}
|
|
32
|
+
async function collectScopeDir(scopeDir, expectedPrefix, seen, matchedNames) {
|
|
14
33
|
let entries;
|
|
15
34
|
try {
|
|
16
35
|
entries = await readdir(scopeDir);
|
|
17
36
|
}
|
|
18
37
|
catch {
|
|
19
|
-
|
|
20
|
-
process.exit(2);
|
|
38
|
+
return;
|
|
21
39
|
}
|
|
22
|
-
const matchedNames = [];
|
|
23
|
-
const expectedPrefix = `@${scope}/`;
|
|
24
40
|
for (const entry of entries) {
|
|
41
|
+
if (entry.startsWith('.'))
|
|
42
|
+
continue;
|
|
25
43
|
const pkgJsonPath = join(scopeDir, entry, 'package.json');
|
|
26
44
|
if (!existsSync(pkgJsonPath))
|
|
27
45
|
continue;
|
|
@@ -35,33 +53,12 @@ async function resolveScopeAlias(scope, rootCwd) {
|
|
|
35
53
|
}
|
|
36
54
|
if (typeof parsed.name === 'string' &&
|
|
37
55
|
parsed.name.startsWith(expectedPrefix) &&
|
|
38
|
-
parsed.name.length > expectedPrefix.length
|
|
56
|
+
parsed.name.length > expectedPrefix.length &&
|
|
57
|
+
!seen.has(parsed.name)) {
|
|
58
|
+
seen.add(parsed.name);
|
|
39
59
|
matchedNames.push(parsed.name);
|
|
40
60
|
}
|
|
41
61
|
}
|
|
42
|
-
if (matchedNames.length === 0) {
|
|
43
|
-
logger.warn(`scope alias "@${scope}" matched no workspace packages under ${scopeDir}.`);
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
const resolved = [];
|
|
47
|
-
for (const name of matchedNames) {
|
|
48
|
-
const meta = await resolvePackage(name, { skipMissingAsset: true });
|
|
49
|
-
if (meta)
|
|
50
|
-
resolved.push(meta);
|
|
51
|
-
}
|
|
52
|
-
return resolved;
|
|
53
|
-
}
|
|
54
|
-
function findPackagesRoot(start) {
|
|
55
|
-
let cur = resolve(start);
|
|
56
|
-
while (true) {
|
|
57
|
-
if (existsSync(join(cur, 'package.json')) && existsSync(join(cur, 'packages'))) {
|
|
58
|
-
return cur;
|
|
59
|
-
}
|
|
60
|
-
const parent = dirname(cur);
|
|
61
|
-
if (parent === cur)
|
|
62
|
-
return null;
|
|
63
|
-
cur = parent;
|
|
64
|
-
}
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
export { resolveScopeAlias };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useCallback } from 'react';
|
|
3
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { VERSION } from '../../utils/version.mjs';
|
|
4
5
|
import { Banner } from '../components/Banner.mjs';
|
|
5
6
|
import { ConfirmForce } from '../components/ConfirmForce.mjs';
|
|
6
7
|
import { ErrorPanel } from '../components/ErrorPanel.mjs';
|
|
@@ -18,7 +19,6 @@ import { colors } from '../theme/colors.mjs';
|
|
|
18
19
|
import { icons } from '../theme/icons.mjs';
|
|
19
20
|
import { scopeLabel, etaSeconds } from './utils/eventSelectors.mjs';
|
|
20
21
|
|
|
21
|
-
const VERSION = '0.3.0';
|
|
22
22
|
function InjectApp(props) {
|
|
23
23
|
const { targets, flags, originCwd, onExit } = props;
|
|
24
24
|
const [phase, dispatch] = usePhase({ kind: 'resolving', targets });
|
|
@@ -14,7 +14,10 @@ function phaseReducer(phase, event) {
|
|
|
14
14
|
return phase;
|
|
15
15
|
}
|
|
16
16
|
case 'planning-started': {
|
|
17
|
-
const progress = new Map(event.targets.map((t) => [
|
|
17
|
+
const progress = new Map(event.targets.map((t) => [
|
|
18
|
+
t.name,
|
|
19
|
+
{ packageName: t.name, status: 'pending' },
|
|
20
|
+
]));
|
|
18
21
|
return {
|
|
19
22
|
kind: 'planning',
|
|
20
23
|
targets: event.targets,
|
|
@@ -46,7 +46,8 @@ function usePlanStep({ targets, scope, originCwd, force, dispatch, onPlansReady,
|
|
|
46
46
|
});
|
|
47
47
|
results.push({ target, scope: scopeResolution, plan });
|
|
48
48
|
for (const action of plan.actions) {
|
|
49
|
-
if (action.kind === 'warn-diverged' ||
|
|
49
|
+
if (action.kind === 'warn-diverged' ||
|
|
50
|
+
action.kind === 'warn-orphan') {
|
|
50
51
|
warnings.push({
|
|
51
52
|
packageName: target.name,
|
|
52
53
|
kind: action.kind,
|
package/dist/ui/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { ConsumerPackage } from '../../commands/runCli/type.js';
|
|
1
2
|
import type { InjectPlan } from '../../core/buildPlan/index.js';
|
|
2
3
|
import type { ScopeResolution } from '../../core/index.js';
|
|
3
|
-
import type { ConsumerPackage } from '../../commands/runCli/type.js';
|
|
4
4
|
export interface PlanStepState {
|
|
5
5
|
readonly packageName: string;
|
|
6
6
|
readonly status: 'pending' | 'running' | 'done' | 'failed';
|
package/dist/utils/version.d.ts
CHANGED
package/dist/utils/version.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slats/claude-assets-sync",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
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",
|
package/scripts/dev-ui.tsx
CHANGED
|
@@ -15,11 +15,10 @@ import { TargetCard } from '../src/ui/components/TargetCard.js';
|
|
|
15
15
|
import { colors } from '../src/ui/theme/colors.js';
|
|
16
16
|
import { icons } from '../src/ui/theme/icons.js';
|
|
17
17
|
import type { Phase } from '../src/ui/types/index.js';
|
|
18
|
+
import { VERSION } from '../src/utils/version.js';
|
|
18
19
|
|
|
19
20
|
import { buildPhase, PHASES, type PhaseKey } from './dev-ui-fixtures.js';
|
|
20
21
|
|
|
21
|
-
const VERSION = '0.3.0-dev';
|
|
22
|
-
|
|
23
22
|
interface CliArgs {
|
|
24
23
|
mode: 'usage' | 'phase' | 'tour';
|
|
25
24
|
phase?: PhaseKey;
|