@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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
6
|
+
|
|
7
|
+
async function resolvePackage(name, options = {}) {
|
|
8
|
+
const pkgJsonPath = resolvePackageJsonPath(name);
|
|
9
|
+
if (!pkgJsonPath) {
|
|
10
|
+
logger.error(`cannot resolve package "${name}". Install it in the current project or pass the correct name.`);
|
|
11
|
+
process.exit(2);
|
|
12
|
+
}
|
|
13
|
+
const packageRoot = dirname(pkgJsonPath);
|
|
14
|
+
const raw = await readFile(pkgJsonPath, 'utf-8');
|
|
15
|
+
const pkg = JSON.parse(raw);
|
|
16
|
+
if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
|
|
17
|
+
if (options.skipMissingAsset) {
|
|
18
|
+
logger.warn(`"${name}" package.json is missing a string "name" or "version" — skipping.`);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
logger.error(`${pkgJsonPath} must define string "name" and "version".`);
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
const assetPath = pkg.claude?.assetPath;
|
|
25
|
+
if (typeof assetPath !== 'string' || assetPath.length === 0) {
|
|
26
|
+
if (options.skipMissingAsset) {
|
|
27
|
+
logger.warn(`"${name}" is missing "claude.assetPath" — skipping (the package does not ship Claude assets).`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
logger.error(`"${name}" is missing "claude.assetPath" in its package.json — the package does not ship Claude assets.`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
packageRoot,
|
|
35
|
+
packageName: pkg.name,
|
|
36
|
+
packageVersion: pkg.version,
|
|
37
|
+
assetPath,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function resolvePackageJsonPath(name) {
|
|
41
|
+
const require = createRequire(import.meta.url);
|
|
42
|
+
try {
|
|
43
|
+
return require.resolve(`${name}/package.json`);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const code = err?.code;
|
|
47
|
+
if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED')
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
let mainEntry;
|
|
51
|
+
try {
|
|
52
|
+
mainEntry = require.resolve(name);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let dir = dirname(mainEntry);
|
|
58
|
+
while (dir && dir !== dirname(dir)) {
|
|
59
|
+
const candidate = resolve(dir, 'package.json');
|
|
60
|
+
if (existsSync(candidate)) {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
63
|
+
if (pkg.name === name)
|
|
64
|
+
return candidate;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
dir = dirname(dir);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { resolvePackage };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_fs = require('node:fs');
|
|
4
|
+
var promises = require('node:fs/promises');
|
|
5
|
+
var node_path = require('node:path');
|
|
6
|
+
var logger = require('../../../utils/logger.cjs');
|
|
7
|
+
var resolvePackage = require('./resolvePackage.cjs');
|
|
8
|
+
|
|
9
|
+
async function resolveScopeAlias(scope, rootCwd) {
|
|
10
|
+
const packagesRoot = findPackagesRoot(rootCwd);
|
|
11
|
+
if (!packagesRoot) {
|
|
12
|
+
logger.logger.error(`cannot locate a monorepo root with a "packages/" directory starting from "${rootCwd}". Scope alias "@${scope}" requires a workspace root.`);
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
const scopeDir = node_path.join(packagesRoot, 'packages', scope);
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = await promises.readdir(scopeDir);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
logger.logger.error(`scope alias "@${scope}" has no matching directory at ${scopeDir}.`);
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
const matchedNames = [];
|
|
25
|
+
const expectedPrefix = `@${scope}/`;
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const pkgJsonPath = node_path.join(scopeDir, entry, 'package.json');
|
|
28
|
+
if (!node_fs.existsSync(pkgJsonPath))
|
|
29
|
+
continue;
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
const raw = await promises.readFile(pkgJsonPath, 'utf-8');
|
|
33
|
+
parsed = JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (typeof parsed.name === 'string' &&
|
|
39
|
+
parsed.name.startsWith(expectedPrefix) &&
|
|
40
|
+
parsed.name.length > expectedPrefix.length) {
|
|
41
|
+
matchedNames.push(parsed.name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (matchedNames.length === 0) {
|
|
45
|
+
logger.logger.warn(`scope alias "@${scope}" matched no workspace packages under ${scopeDir}.`);
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const resolved = [];
|
|
49
|
+
for (const name of matchedNames) {
|
|
50
|
+
const meta = await resolvePackage.resolvePackage(name, { skipMissingAsset: true });
|
|
51
|
+
if (meta)
|
|
52
|
+
resolved.push(meta);
|
|
53
|
+
}
|
|
54
|
+
return resolved;
|
|
55
|
+
}
|
|
56
|
+
function findPackagesRoot(start) {
|
|
57
|
+
let cur = node_path.resolve(start);
|
|
58
|
+
while (true) {
|
|
59
|
+
if (node_fs.existsSync(node_path.join(cur, 'package.json')) && node_fs.existsSync(node_path.join(cur, 'packages'))) {
|
|
60
|
+
return cur;
|
|
61
|
+
}
|
|
62
|
+
const parent = node_path.dirname(cur);
|
|
63
|
+
if (parent === cur)
|
|
64
|
+
return null;
|
|
65
|
+
cur = parent;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exports.resolveScopeAlias = resolveScopeAlias;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve, dirname } from 'node:path';
|
|
4
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
5
|
+
import { resolvePackage } from './resolvePackage.mjs';
|
|
6
|
+
|
|
7
|
+
async function resolveScopeAlias(scope, rootCwd) {
|
|
8
|
+
const packagesRoot = findPackagesRoot(rootCwd);
|
|
9
|
+
if (!packagesRoot) {
|
|
10
|
+
logger.error(`cannot locate a monorepo root with a "packages/" directory starting from "${rootCwd}". Scope alias "@${scope}" requires a workspace root.`);
|
|
11
|
+
process.exit(2);
|
|
12
|
+
}
|
|
13
|
+
const scopeDir = join(packagesRoot, 'packages', scope);
|
|
14
|
+
let entries;
|
|
15
|
+
try {
|
|
16
|
+
entries = await readdir(scopeDir);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
logger.error(`scope alias "@${scope}" has no matching directory at ${scopeDir}.`);
|
|
20
|
+
process.exit(2);
|
|
21
|
+
}
|
|
22
|
+
const matchedNames = [];
|
|
23
|
+
const expectedPrefix = `@${scope}/`;
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const pkgJsonPath = join(scopeDir, entry, 'package.json');
|
|
26
|
+
if (!existsSync(pkgJsonPath))
|
|
27
|
+
continue;
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
const raw = await readFile(pkgJsonPath, 'utf-8');
|
|
31
|
+
parsed = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (typeof parsed.name === 'string' &&
|
|
37
|
+
parsed.name.startsWith(expectedPrefix) &&
|
|
38
|
+
parsed.name.length > expectedPrefix.length) {
|
|
39
|
+
matchedNames.push(parsed.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
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
|
+
}
|
|
66
|
+
|
|
67
|
+
export { resolveScopeAlias };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var logger = require('../../../utils/logger.cjs');
|
|
4
|
+
var classifyTarget = require('./classifyTarget.cjs');
|
|
5
|
+
var resolvePackage = require('./resolvePackage.cjs');
|
|
6
|
+
var resolveScopeAlias = require('./resolveScopeAlias.cjs');
|
|
7
|
+
|
|
8
|
+
async function resolveTargets(targets, rootCwd) {
|
|
9
|
+
if (targets.length === 0)
|
|
10
|
+
return [];
|
|
11
|
+
const isSingleTarget = targets.length === 1;
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const results = [];
|
|
14
|
+
for (const target of targets) {
|
|
15
|
+
const classification = classifyTarget.classifyTarget(target);
|
|
16
|
+
if (classification.kind === 'invalid') {
|
|
17
|
+
logger.logger.error(classification.reason);
|
|
18
|
+
process.exit(2);
|
|
19
|
+
}
|
|
20
|
+
let candidates;
|
|
21
|
+
if (classification.kind === 'scope') {
|
|
22
|
+
candidates = await resolveScopeAlias.resolveScopeAlias(classification.scope, rootCwd);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const meta = await resolvePackage.resolvePackage(classification.name, {
|
|
26
|
+
skipMissingAsset: !isSingleTarget,
|
|
27
|
+
});
|
|
28
|
+
candidates = meta ? [meta] : [];
|
|
29
|
+
}
|
|
30
|
+
for (const meta of candidates) {
|
|
31
|
+
if (!seen.has(meta.packageName)) {
|
|
32
|
+
seen.add(meta.packageName);
|
|
33
|
+
results.push(meta);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.resolveTargets = resolveTargets;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ResolvedMetadata } from './resolvePackage.js';
|
|
2
|
+
/**
|
|
3
|
+
* Classify each `--package` value, resolve them all, and dedupe the
|
|
4
|
+
* result by `packageName`.
|
|
5
|
+
*
|
|
6
|
+
* - `@<scope>` values enumerate through `resolveScopeAlias` (soft skip
|
|
7
|
+
* when a workspace package lacks `claude.assetPath`).
|
|
8
|
+
* - `@<scope>/<name>` and `<name>` values go through `resolvePackage`.
|
|
9
|
+
* When there is a single `--package` value, the call is strict
|
|
10
|
+
* (asset-missing → exit 2); otherwise asset-missing is a soft skip
|
|
11
|
+
* so the rest of the batch can proceed.
|
|
12
|
+
*
|
|
13
|
+
* Invalid `--package` values exit with code 2 before any filesystem IO.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveTargets(targets: readonly string[], rootCwd: string): Promise<ResolvedMetadata[]>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
2
|
+
import { classifyTarget } from './classifyTarget.mjs';
|
|
3
|
+
import { resolvePackage } from './resolvePackage.mjs';
|
|
4
|
+
import { resolveScopeAlias } from './resolveScopeAlias.mjs';
|
|
5
|
+
|
|
6
|
+
async function resolveTargets(targets, rootCwd) {
|
|
7
|
+
if (targets.length === 0)
|
|
8
|
+
return [];
|
|
9
|
+
const isSingleTarget = targets.length === 1;
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const target of targets) {
|
|
13
|
+
const classification = classifyTarget(target);
|
|
14
|
+
if (classification.kind === 'invalid') {
|
|
15
|
+
logger.error(classification.reason);
|
|
16
|
+
process.exit(2);
|
|
17
|
+
}
|
|
18
|
+
let candidates;
|
|
19
|
+
if (classification.kind === 'scope') {
|
|
20
|
+
candidates = await resolveScopeAlias(classification.scope, rootCwd);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const meta = await resolvePackage(classification.name, {
|
|
24
|
+
skipMissingAsset: !isSingleTarget,
|
|
25
|
+
});
|
|
26
|
+
candidates = meta ? [meta] : [];
|
|
27
|
+
}
|
|
28
|
+
for (const meta of candidates) {
|
|
29
|
+
if (!seen.has(meta.packageName)) {
|
|
30
|
+
seen.add(meta.packageName);
|
|
31
|
+
results.push(meta);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { resolveTargets };
|
|
@@ -6,31 +6,47 @@ var logger = require('../../../utils/logger.cjs');
|
|
|
6
6
|
var injectOne = require('./injectOne.cjs');
|
|
7
7
|
var resolveScopeFlag = require('./resolveScopeFlag.cjs');
|
|
8
8
|
|
|
9
|
-
async function runInject(flags,
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
!
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
async function runInject(flags, metadataList) {
|
|
10
|
+
if (metadataList.length === 0)
|
|
11
|
+
return;
|
|
12
|
+
for (const metadata of metadataList) {
|
|
13
|
+
if (!node_path.isAbsolute(metadata.packageRoot)) {
|
|
14
|
+
logger.logger.error(`packageRoot must be an absolute path; received: ${metadata.packageRoot}`);
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
16
17
|
}
|
|
17
|
-
if (!node_path.isAbsolute(options.packageRoot)) {
|
|
18
|
-
logger.logger.error(`packageRoot must be an absolute path; received: ${options.packageRoot}`);
|
|
19
|
-
process.exit(2);
|
|
20
|
-
}
|
|
21
|
-
const assetRoot = node_path.resolve(options.packageRoot, options.assetPath);
|
|
22
|
-
const hashesPath = node_path.join(options.packageRoot, 'dist', 'claude-hashes.json');
|
|
23
|
-
const hashesPresent = await promises.stat(hashesPath).then(() => true, () => false);
|
|
24
|
-
const target = {
|
|
25
|
-
name: options.packageName,
|
|
26
|
-
version: options.packageVersion,
|
|
27
|
-
packageRoot: options.packageRoot,
|
|
28
|
-
assetRoot,
|
|
29
|
-
hashesPresent,
|
|
30
|
-
};
|
|
31
18
|
const originCwd = flags.root ?? process.cwd();
|
|
32
19
|
const scope = await resolveScopeFlag.resolveScopeFlag(flags.scope);
|
|
33
|
-
|
|
20
|
+
const fatalOnError = metadataList.length === 1;
|
|
21
|
+
let failureCount = 0;
|
|
22
|
+
for (const metadata of metadataList) {
|
|
23
|
+
const assetRoot = node_path.resolve(metadata.packageRoot, metadata.assetPath);
|
|
24
|
+
const hashesPath = node_path.join(metadata.packageRoot, 'dist', 'claude-hashes.json');
|
|
25
|
+
const hashesPresent = await promises.stat(hashesPath).then(() => true, () => false);
|
|
26
|
+
const target = {
|
|
27
|
+
name: metadata.packageName,
|
|
28
|
+
version: metadata.packageVersion,
|
|
29
|
+
packageRoot: metadata.packageRoot,
|
|
30
|
+
assetRoot,
|
|
31
|
+
hashesPresent,
|
|
32
|
+
};
|
|
33
|
+
let exitCode;
|
|
34
|
+
try {
|
|
35
|
+
exitCode = await injectOne.injectOne(target, scope, flags, originCwd);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
39
|
+
logger.logger.error(`${target.name}: ${msg}`);
|
|
40
|
+
exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
if (exitCode !== 0) {
|
|
43
|
+
if (fatalOnError)
|
|
44
|
+
process.exit(exitCode);
|
|
45
|
+
failureCount += 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (failureCount > 0)
|
|
49
|
+
process.exit(1);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
exports.runInject = runInject;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { DefaultFlags
|
|
2
|
-
|
|
1
|
+
import type { DefaultFlags } from '../type.js';
|
|
2
|
+
import type { ResolvedMetadata } from './resolvePackage.js';
|
|
3
|
+
export declare function runInject(flags: DefaultFlags, metadataList: readonly ResolvedMetadata[]): Promise<void>;
|
|
@@ -4,31 +4,47 @@ import { logger } from '../../../utils/logger.mjs';
|
|
|
4
4
|
import { injectOne } from './injectOne.mjs';
|
|
5
5
|
import { resolveScopeFlag } from './resolveScopeFlag.mjs';
|
|
6
6
|
|
|
7
|
-
async function runInject(flags,
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
!
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
async function runInject(flags, metadataList) {
|
|
8
|
+
if (metadataList.length === 0)
|
|
9
|
+
return;
|
|
10
|
+
for (const metadata of metadataList) {
|
|
11
|
+
if (!isAbsolute(metadata.packageRoot)) {
|
|
12
|
+
logger.error(`packageRoot must be an absolute path; received: ${metadata.packageRoot}`);
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
14
15
|
}
|
|
15
|
-
if (!isAbsolute(options.packageRoot)) {
|
|
16
|
-
logger.error(`packageRoot must be an absolute path; received: ${options.packageRoot}`);
|
|
17
|
-
process.exit(2);
|
|
18
|
-
}
|
|
19
|
-
const assetRoot = resolve(options.packageRoot, options.assetPath);
|
|
20
|
-
const hashesPath = join(options.packageRoot, 'dist', 'claude-hashes.json');
|
|
21
|
-
const hashesPresent = await stat(hashesPath).then(() => true, () => false);
|
|
22
|
-
const target = {
|
|
23
|
-
name: options.packageName,
|
|
24
|
-
version: options.packageVersion,
|
|
25
|
-
packageRoot: options.packageRoot,
|
|
26
|
-
assetRoot,
|
|
27
|
-
hashesPresent,
|
|
28
|
-
};
|
|
29
16
|
const originCwd = flags.root ?? process.cwd();
|
|
30
17
|
const scope = await resolveScopeFlag(flags.scope);
|
|
31
|
-
|
|
18
|
+
const fatalOnError = metadataList.length === 1;
|
|
19
|
+
let failureCount = 0;
|
|
20
|
+
for (const metadata of metadataList) {
|
|
21
|
+
const assetRoot = resolve(metadata.packageRoot, metadata.assetPath);
|
|
22
|
+
const hashesPath = join(metadata.packageRoot, 'dist', 'claude-hashes.json');
|
|
23
|
+
const hashesPresent = await stat(hashesPath).then(() => true, () => false);
|
|
24
|
+
const target = {
|
|
25
|
+
name: metadata.packageName,
|
|
26
|
+
version: metadata.packageVersion,
|
|
27
|
+
packageRoot: metadata.packageRoot,
|
|
28
|
+
assetRoot,
|
|
29
|
+
hashesPresent,
|
|
30
|
+
};
|
|
31
|
+
let exitCode;
|
|
32
|
+
try {
|
|
33
|
+
exitCode = await injectOne(target, scope, flags, originCwd);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37
|
+
logger.error(`${target.name}: ${msg}`);
|
|
38
|
+
exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
if (exitCode !== 0) {
|
|
41
|
+
if (fatalOnError)
|
|
42
|
+
process.exit(exitCode);
|
|
43
|
+
failureCount += 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (failureCount > 0)
|
|
47
|
+
process.exit(1);
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
export { runInject };
|
|
@@ -13,7 +13,7 @@ async function applyAction(action, assetRoot) {
|
|
|
13
13
|
else if (action.kind === 'delete')
|
|
14
14
|
await promises.unlink(action.dstAbs).catch((error) => {
|
|
15
15
|
if (error?.code !== 'ENOENT') {
|
|
16
|
-
logger.logger.warn(`[claude-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
|
|
16
|
+
logger.logger.warn(`[claude-assets-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
}
|
|
@@ -11,7 +11,7 @@ async function applyAction(action, assetRoot) {
|
|
|
11
11
|
else if (action.kind === 'delete')
|
|
12
12
|
await unlink(action.dstAbs).catch((error) => {
|
|
13
13
|
if (error?.code !== 'ENOENT') {
|
|
14
|
-
logger.warn(`[claude-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
|
|
14
|
+
logger.warn(`[claude-assets-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
|
|
15
15
|
}
|
|
16
16
|
});
|
|
17
17
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { runCli
|
|
1
|
+
export { runCli } from './commands/index.js';
|
|
2
2
|
export { HASH_MANIFEST_FILENAME, computeNamespacePrefixes, injectDocs, isInteractive, isValidScope, readHashManifest, resolveScope, type HashManifest, type InjectOptions, type InjectReport, type Scope, type ScopeResolution, } from './core/index.js';
|
|
3
3
|
export type { AssetType } from './utils/types.js';
|
package/dist/utils/version.cjs
CHANGED
package/dist/utils/version.d.ts
CHANGED
package/dist/utils/version.mjs
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: claude-docs-asset-wiring
|
|
3
|
+
description: "Wire a consumer package's docs/claude assets into the @slats/claude-assets-sync engine. Adds package.json.claude.assetPath, points scripts.build:hashes at claude-build-hashes, declares the engine as a dependency, updates CLAUDE.md, and runs the dispatcher smoke test. Idempotent — asks before clobbering."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
argument-hint: <target-package-path>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# claude-docs-asset-wiring
|
|
10
|
+
|
|
11
|
+
Wire a consumer package's `docs/claude/**` into `@slats/claude-assets-sync`
|
|
12
|
+
so end users can inject those assets via the engine's `inject-claude-settings`
|
|
13
|
+
bin. Reference consumer: `packages/canard/schema-form`.
|
|
14
|
+
|
|
15
|
+
The engine is single-dispatcher. Consumers do NOT ship their own bin stubs
|
|
16
|
+
— they declare `claude.assetPath` in `package.json` and let the engine's
|
|
17
|
+
`claude-build-hashes` bin regenerate `dist/claude-hashes.json` during
|
|
18
|
+
build. `src/core/**` never reads `package.json`; only the engine's `bin/`
|
|
19
|
+
layer resolves a single explicitly-named target.
|
|
20
|
+
|
|
21
|
+
**Outcome**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx -p @slats/claude-assets-sync inject-claude-settings \
|
|
25
|
+
--package=<PACKAGE_NAME> \
|
|
26
|
+
--scope=user|project [--dry-run] [--force]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Role
|
|
30
|
+
|
|
31
|
+
You are a monorepo wiring specialist. Execute the 6 steps below as a
|
|
32
|
+
single, idempotent procedure. On any conflicting existing value — ask
|
|
33
|
+
the user before overwriting. Never clobber silently.
|
|
34
|
+
|
|
35
|
+
## Knowledge Resources
|
|
36
|
+
|
|
37
|
+
Consult these files as needed during execution. Do NOT preload everything;
|
|
38
|
+
load on demand.
|
|
39
|
+
|
|
40
|
+
- `knowledge/reference-files.md` — what the consumer should (and should not) own
|
|
41
|
+
- `knowledge/package-json-patches.md` — every required `package.json` edit, with guard conditions
|
|
42
|
+
- `knowledge/claude-md-template.md` — the `## Claude Docs Injector` section to inject into the target `CLAUDE.md`
|
|
43
|
+
- `knowledge/smoke-tests.md` — E2E 8-path matrix via the engine dispatcher
|
|
44
|
+
- `knowledge/dependency-cruiser.md` — optional CI-time isolation rule
|
|
45
|
+
- `knowledge/gotchas.md` — invariants and pitfalls
|
|
46
|
+
|
|
47
|
+
## Inputs
|
|
48
|
+
|
|
49
|
+
Resolve these before starting. If any is missing, stop and ask.
|
|
50
|
+
|
|
51
|
+
| Variable | Source |
|
|
52
|
+
|----------------|-----------------------------------------------------------------------------------------------------------|
|
|
53
|
+
| `TARGET_PATH` | Skill argument (e.g. `packages/lerx/promise-modal`). If absent, ask the user. |
|
|
54
|
+
| `PACKAGE_NAME` | `name` field of `${TARGET_PATH}/package.json`. |
|
|
55
|
+
| `SHORTCUT` | Root `package.json` `scripts` entry whose value equals `yarn workspace ${PACKAGE_NAME}`; else unset. |
|
|
56
|
+
|
|
57
|
+
`SHORTCUT` is a convenience only. When unset, fall back to full workspace
|
|
58
|
+
syntax: `yarn workspace ${PACKAGE_NAME} <subcommand>`.
|
|
59
|
+
|
|
60
|
+
## Pre-Flight
|
|
61
|
+
|
|
62
|
+
Stop and report on any failure. Do not attempt to fix silently.
|
|
63
|
+
|
|
64
|
+
- [ ] `${TARGET_PATH}/docs/claude/skills/<name>/SKILL.md` and `knowledge/*.md` exist — the docs to be injected.
|
|
65
|
+
- [ ] `${TARGET_PATH}/package.json` has `"type": "module"` and `"sideEffects": false`.
|
|
66
|
+
- [ ] Build pipeline uses `rollup -c && yarn build:types` where `build:types` runs `node ../../aileron/script/build/buildTypes.mjs`.
|
|
67
|
+
- [ ] `git status` in `${TARGET_PATH}` is clean. Unrelated changes present → confirm with user before proceeding.
|
|
68
|
+
|
|
69
|
+
## Steps
|
|
70
|
+
|
|
71
|
+
Execute in order. Each step is idempotent; on conflict, ask rather than overwrite.
|
|
72
|
+
|
|
73
|
+
### Step 1 — Patch `${TARGET_PATH}/package.json`
|
|
74
|
+
|
|
75
|
+
See `knowledge/package-json-patches.md` for the complete patch list:
|
|
76
|
+
|
|
77
|
+
- `claude.assetPath` — set to `docs/claude` (or the consumer's chosen path).
|
|
78
|
+
- `scripts.build` — ensure the chain ends with `&& yarn build:hashes`.
|
|
79
|
+
- `scripts.build:hashes` — set to `claude-build-hashes` (the engine's bin).
|
|
80
|
+
- `scripts.prepublishOnly` — `yarn build` if not already present.
|
|
81
|
+
- `dependencies."@slats/claude-assets-sync"` — add (NOT `devDependencies`, NOT `peerDependencies`).
|
|
82
|
+
- `files` — ensure `"dist"`, `"docs"`, `"README.md"` are listed. Never include `"bin"` or `"scripts"`.
|
|
83
|
+
|
|
84
|
+
Do NOT add any `bin` entry. Do NOT add `./bin/*` or `./docs/*` to `exports`.
|
|
85
|
+
Do NOT create `bin/` or `scripts/` directories in the consumer.
|
|
86
|
+
|
|
87
|
+
### Step 2 — Patch `${TARGET_PATH}/CLAUDE.md`
|
|
88
|
+
|
|
89
|
+
If `CLAUDE.md` exists, append or replace the `## Claude Docs Injector`
|
|
90
|
+
section from `knowledge/claude-md-template.md`, substituting
|
|
91
|
+
`${PACKAGE_NAME}`. Skip if `CLAUDE.md` does not exist (do not create one).
|
|
92
|
+
|
|
93
|
+
### Step 3 — (Optional) Dependency-cruiser isolation gate
|
|
94
|
+
|
|
95
|
+
Skip unless `${TARGET_PATH}/.dependency-cruiser.cjs` already exists or the
|
|
96
|
+
user explicitly asks. See `knowledge/dependency-cruiser.md` for the single
|
|
97
|
+
remaining forbidden rule (`src/**` → `docs/**`) and the `depcheck` script.
|
|
98
|
+
|
|
99
|
+
### Step 4 — Install and build
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
yarn install
|
|
103
|
+
yarn ${SHORTCUT:-workspace ${PACKAGE_NAME}} build
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Expected: `rollup` → `buildTypes` → `claude-build-hashes` succeed, and
|
|
107
|
+
`${TARGET_PATH}/dist/claude-hashes.json` is written.
|
|
108
|
+
|
|
109
|
+
### Step 5 — E2E smoke via engine dispatcher
|
|
110
|
+
|
|
111
|
+
Run from `/tmp/...`, never from the monorepo root or `${TARGET_PATH}/` —
|
|
112
|
+
`--scope=project` walks `cwd` upward looking for an existing `.claude`,
|
|
113
|
+
which would mutate the real repo's. See `knowledge/smoke-tests.md` for
|
|
114
|
+
the full 8-path matrix, expected exit codes, and rationale.
|
|
115
|
+
|
|
116
|
+
### Step 6 — Report
|
|
117
|
+
|
|
118
|
+
Summarize:
|
|
119
|
+
|
|
120
|
+
- Files patched vs. skipped (with reason for each skip).
|
|
121
|
+
- Manifest file count from `dist/claude-hashes.json`.
|
|
122
|
+
- Smoke-test exit codes (all 8).
|
|
123
|
+
- Recommendation: commit this change on its own, separate from other work.
|
|
124
|
+
|
|
125
|
+
## Report Template
|
|
126
|
+
|
|
127
|
+
```markdown
|
|
128
|
+
## claude-docs-asset-wiring — ${PACKAGE_NAME}
|
|
129
|
+
|
|
130
|
+
**Files patched**
|
|
131
|
+
- package.json — patched: [claude.assetPath, scripts.build, scripts.build:hashes, dependencies, files]
|
|
132
|
+
- CLAUDE.md — section added | skipped (no CLAUDE.md)
|
|
133
|
+
- .dependency-cruiser.cjs — updated | skipped (not present)
|
|
134
|
+
|
|
135
|
+
**Manifest**
|
|
136
|
+
- dist/claude-hashes.json: <N> files
|
|
137
|
+
|
|
138
|
+
**Smoke tests**
|
|
139
|
+
| # | command | expected | actual |
|
|
140
|
+
|---|-------------------------------------------------------------|----------|--------|
|
|
141
|
+
| 1 | --package=${PACKAGE_NAME} --scope=project --dry-run | 0 | <n> |
|
|
142
|
+
| 2 | --package=${PACKAGE_NAME} --scope=project | 0 | <n> |
|
|
143
|
+
| 3 | --package=${PACKAGE_NAME} --scope=project (up-to-date) | 0 | <n> |
|
|
144
|
+
| 4 | CI=true --package=${PACKAGE_NAME} --scope=project (tampered)| 2 | <n> |
|
|
145
|
+
| 5 | CI=true --package=${PACKAGE_NAME} --scope=project --force | 0 | <n> |
|
|
146
|
+
| 6 | CI=true --package=${PACKAGE_NAME} (missing --scope) | 2 | <n> |
|
|
147
|
+
| 7 | (missing --package) | 2 | <n> |
|
|
148
|
+
| 8 | --package=@does/not-exist | 2 | <n> |
|
|
149
|
+
|
|
150
|
+
**Next**: commit on its own — do not bundle with unrelated changes.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Termination Conditions
|
|
154
|
+
|
|
155
|
+
- **Pre-Flight fails** → stop, report the failing check. Do not proceed.
|
|
156
|
+
- **Conflict during patch** → stop, show the diff, ask user whether to overwrite.
|
|
157
|
+
- **Build fails at Step 4** → stop, report error. Do not run smoke tests on a broken build.
|
|
158
|
+
- **Smoke test mismatch** → stop, report the failing path with captured exit code.
|
|
159
|
+
- **All steps pass** → emit the report from the template above.
|