@slats/claude-assets-sync 0.2.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.
Files changed (158) hide show
  1. package/README.md +47 -63
  2. package/bin/inject-claude-settings.mjs +4 -0
  3. package/dist/claude-hashes.json +9 -9
  4. package/dist/commands/index.d.ts +1 -1
  5. package/dist/commands/runCli/index.d.ts +1 -1
  6. package/dist/commands/runCli/runCli.d.ts +10 -6
  7. package/dist/commands/runCli/runCli.mjs +33 -6
  8. package/dist/commands/runCli/type.d.ts +4 -12
  9. package/dist/commands/runCli/utils/classifyTarget.d.ts +19 -0
  10. package/dist/commands/runCli/utils/classifyTarget.mjs +46 -0
  11. package/dist/commands/runCli/utils/renderOrFallback.d.ts +6 -0
  12. package/dist/commands/runCli/utils/renderOrFallback.mjs +12 -0
  13. package/dist/commands/runCli/utils/renderPlain.d.ts +11 -0
  14. package/dist/commands/runCli/utils/renderPlain.mjs +89 -0
  15. package/dist/commands/runCli/utils/resolvePackage.d.ts +16 -0
  16. package/dist/commands/runCli/utils/resolvePackage.mjs +74 -0
  17. package/dist/commands/runCli/utils/resolveScopeAlias.d.ts +2 -0
  18. package/dist/commands/runCli/utils/resolveScopeAlias.mjs +67 -0
  19. package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +9 -1
  20. package/dist/commands/runCli/utils/resolveScopeFlag.mjs +5 -11
  21. package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
  22. package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
  23. package/dist/commands/runCli/utils/toConsumerPackages.d.ts +9 -0
  24. package/dist/commands/runCli/utils/toConsumerPackages.mjs +26 -0
  25. package/dist/core/index.d.ts +2 -2
  26. package/dist/core/injectDocs/index.d.ts +3 -2
  27. package/dist/core/injectDocs/type.d.ts +0 -19
  28. package/dist/core/injectDocs/utils/applyAction.mjs +1 -1
  29. package/dist/core/scope/index.d.ts +1 -1
  30. package/dist/core/scope/scope.d.ts +0 -1
  31. package/dist/core/scope/scope.mjs +1 -4
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.mjs +2 -2
  34. package/dist/ui/InjectApp/InjectApp.d.ts +2 -0
  35. package/dist/ui/InjectApp/InjectApp.mjs +82 -0
  36. package/dist/ui/InjectApp/index.d.ts +2 -0
  37. package/dist/ui/InjectApp/utils/eventSelectors.d.ts +5 -0
  38. package/dist/ui/InjectApp/utils/eventSelectors.mjs +24 -0
  39. package/dist/ui/InjectApp/utils/phaseReducer.d.ts +2 -0
  40. package/dist/ui/InjectApp/utils/phaseReducer.mjs +130 -0
  41. package/dist/ui/InjectApp/utils/renderInjectApp.d.ts +2 -0
  42. package/dist/ui/InjectApp/utils/renderInjectApp.mjs +19 -0
  43. package/dist/ui/InjectApp/utils/type.d.ts +5 -0
  44. package/dist/ui/components/ActionRow.d.ts +7 -0
  45. package/dist/ui/components/ActionRow.mjs +45 -0
  46. package/dist/ui/components/Banner.d.ts +7 -0
  47. package/dist/ui/components/Banner.mjs +9 -0
  48. package/dist/ui/components/ConfirmForce.d.ts +8 -0
  49. package/dist/ui/components/ConfirmForce.mjs +35 -0
  50. package/dist/ui/components/ErrorPanel.d.ts +6 -0
  51. package/dist/ui/components/ErrorPanel.mjs +14 -0
  52. package/dist/ui/components/Footer.d.ts +8 -0
  53. package/dist/ui/components/Footer.mjs +27 -0
  54. package/dist/ui/components/PlanTable.d.ts +8 -0
  55. package/dist/ui/components/PlanTable.mjs +15 -0
  56. package/dist/ui/components/ProgressBar.d.ts +10 -0
  57. package/dist/ui/components/ProgressBar.mjs +28 -0
  58. package/dist/ui/components/ScopePicker.d.ts +7 -0
  59. package/dist/ui/components/ScopePicker.mjs +26 -0
  60. package/dist/ui/components/Spinner.d.ts +8 -0
  61. package/dist/ui/components/Spinner.mjs +10 -0
  62. package/dist/ui/components/StatusBadge.d.ts +8 -0
  63. package/dist/ui/components/StepTracker.d.ts +9 -0
  64. package/dist/ui/components/StepTracker.mjs +43 -0
  65. package/dist/ui/components/Summary.d.ts +9 -0
  66. package/dist/ui/components/Summary.mjs +30 -0
  67. package/dist/ui/components/TargetCard.d.ts +11 -0
  68. package/dist/ui/components/TargetCard.mjs +29 -0
  69. package/dist/ui/hooks/useApplyStep.d.ts +12 -0
  70. package/dist/ui/hooks/useApplyStep.mjs +30 -0
  71. package/dist/ui/hooks/useExitApp.d.ts +8 -0
  72. package/dist/ui/hooks/useExitApp.mjs +19 -0
  73. package/dist/ui/hooks/useForceConfirmStep.d.ts +9 -0
  74. package/dist/ui/hooks/useForceConfirmStep.mjs +24 -0
  75. package/dist/ui/hooks/useInjectSession.d.ts +10 -0
  76. package/dist/ui/hooks/useInjectSession.mjs +63 -0
  77. package/dist/ui/hooks/useInterval.d.ts +1 -0
  78. package/dist/ui/hooks/usePhase.d.ts +2 -0
  79. package/dist/ui/hooks/usePhase.mjs +9 -0
  80. package/dist/ui/hooks/usePlanStep.d.ts +13 -0
  81. package/dist/ui/hooks/usePlanStep.mjs +94 -0
  82. package/dist/ui/hooks/useResolveStep.d.ts +18 -0
  83. package/dist/ui/hooks/useResolveStep.mjs +21 -0
  84. package/dist/ui/hooks/useTerminalWidth.d.ts +1 -0
  85. package/dist/ui/index.d.ts +2 -0
  86. package/dist/ui/index.mjs +16 -0
  87. package/dist/ui/theme/colors.d.ts +12 -0
  88. package/dist/ui/theme/colors.mjs +9 -0
  89. package/dist/ui/theme/icons.d.ts +29 -0
  90. package/dist/ui/theme/icons.mjs +17 -0
  91. package/dist/ui/theme/layout.d.ts +20 -0
  92. package/dist/ui/theme/layout.mjs +9 -0
  93. package/dist/ui/types/event.d.ts +45 -0
  94. package/dist/ui/types/index.d.ts +4 -0
  95. package/dist/ui/types/phase.d.ts +44 -0
  96. package/dist/ui/types/render.d.ts +6 -0
  97. package/dist/ui/types/target.d.ts +25 -0
  98. package/dist/utils/version.d.ts +1 -1
  99. package/dist/utils/version.mjs +1 -1
  100. package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
  101. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +78 -0
  102. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
  103. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +125 -0
  104. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +150 -0
  105. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
  106. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
  107. package/docs/consumer-integration.md +43 -101
  108. package/package.json +13 -8
  109. package/scripts/dev-ui-fixtures.ts +288 -0
  110. package/scripts/dev-ui.tsx +289 -0
  111. package/bin/claude-sync.mjs +0 -24
  112. package/dist/commands/runCli/runCli.cjs +0 -31
  113. package/dist/commands/runCli/utils/injectOne.cjs +0 -48
  114. package/dist/commands/runCli/utils/injectOne.d.ts +0 -3
  115. package/dist/commands/runCli/utils/injectOne.mjs +0 -46
  116. package/dist/commands/runCli/utils/resolveScopeFlag.cjs +0 -28
  117. package/dist/commands/runCli/utils/runInject.cjs +0 -36
  118. package/dist/commands/runCli/utils/runInject.d.ts +0 -2
  119. package/dist/commands/runCli/utils/runInject.mjs +0 -34
  120. package/dist/core/buildPlan/buildPlan.cjs +0 -42
  121. package/dist/core/buildPlan/utils/toPosix.cjs +0 -9
  122. package/dist/core/buildPlan/utils/walkFiles.cjs +0 -25
  123. package/dist/core/hash/hash.cjs +0 -30
  124. package/dist/core/hashManifest/hashManifest.cjs +0 -27
  125. package/dist/core/injectDocs/injectDocs.cjs +0 -43
  126. package/dist/core/injectDocs/injectDocs.d.ts +0 -2
  127. package/dist/core/injectDocs/injectDocs.mjs +0 -41
  128. package/dist/core/injectDocs/utils/applyAction.cjs +0 -21
  129. package/dist/core/injectDocs/utils/emitCiForceList.cjs +0 -10
  130. package/dist/core/injectDocs/utils/emitCiForceList.d.ts +0 -2
  131. package/dist/core/injectDocs/utils/emitCiForceList.mjs +0 -8
  132. package/dist/core/injectDocs/utils/printPlan.cjs +0 -20
  133. package/dist/core/injectDocs/utils/printPlan.d.ts +0 -2
  134. package/dist/core/injectDocs/utils/printPlan.mjs +0 -18
  135. package/dist/core/injectDocs/utils/summarize.cjs +0 -27
  136. package/dist/core/scope/scope.cjs +0 -46
  137. package/dist/core/scope/utils/isDirectory.cjs +0 -14
  138. package/dist/index.cjs +0 -20
  139. package/dist/prompts/confirmForce.cjs +0 -27
  140. package/dist/prompts/confirmForce.d.ts +0 -1
  141. package/dist/prompts/confirmForce.mjs +0 -25
  142. package/dist/prompts/index.d.ts +0 -2
  143. package/dist/prompts/selectScope.cjs +0 -30
  144. package/dist/prompts/selectScope.d.ts +0 -2
  145. package/dist/prompts/selectScope.mjs +0 -28
  146. package/dist/utils/asyncPool.cjs +0 -26
  147. package/dist/utils/heartbeat.cjs +0 -25
  148. package/dist/utils/heartbeat.d.ts +0 -16
  149. package/dist/utils/heartbeat.mjs +0 -23
  150. package/dist/utils/logger.cjs +0 -74
  151. package/dist/utils/version.cjs +0 -5
  152. package/docs/claude/skills/claude-sync-applier/SKILL.md +0 -195
  153. package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +0 -77
  154. package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +0 -126
  155. package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +0 -139
  156. package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +0 -130
  157. package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +0 -120
  158. package/docs/claude/skills/claude-sync-applier/knowledge/smoke-tests.md +0 -102
@@ -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 };
@@ -1,2 +1,10 @@
1
1
  import { type Scope } from '../../../core/index.js';
2
- export declare function resolveScopeFlag(flag: string | undefined): Promise<Scope>;
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, isInteractive } from '../../../core/scope/scope.mjs';
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
- async function resolveScopeFlag(flag) {
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
- if (!isInteractive()) {
19
- logger.error('--scope is required in non-interactive environments.');
20
- logger.error(' Pass --scope=user | --scope=project.');
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,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 };
@@ -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 };
@@ -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 { injectDocs, type InjectOptions, type InjectReport, } from './injectDocs/index.js';
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, isInteractive, isValidScope, resolveScope, type Scope, type ScopeResolution, } from './scope/index.js';
5
+ export { findNearestDotClaudeAncestor, isValidScope, resolveScope, type Scope, type ScopeResolution, } from './scope/index.js';
@@ -1,2 +1,3 @@
1
- export { injectDocs } from './injectDocs.js';
2
- export type { InjectOptions, InjectReport } from './type.js';
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[];
@@ -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
  }
@@ -1 +1 @@
1
- export { findNearestDotClaudeAncestor, isInteractive, isValidScope, resolveScope, type Scope, type ScopeResolution, } from './scope.js';
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, isInteractive, isValidScope, resolveScope };
38
+ export { findNearestDotClaudeAncestor, isValidScope, resolveScope };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { runCli, type RunCliOptions } from './commands/index.js';
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';
1
+ export { runCli } from './commands/index.js';
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
- export { isInteractive, isValidScope, resolveScope } from './core/scope/scope.mjs';
6
+ import 'picocolors';
7
+ export { isValidScope, resolveScope } from './core/scope/scope.mjs';
@@ -0,0 +1,2 @@
1
+ import type { InjectAppProps } from './utils/type.js';
2
+ export declare function InjectApp(props: InjectAppProps): React.ReactElement;
@@ -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,2 @@
1
+ export { renderInjectApp } from './utils/renderInjectApp.js';
2
+ export { InjectApp } from './InjectApp.js';
@@ -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 };
@@ -0,0 +1,2 @@
1
+ import type { InjectEvent, Phase } from '../../types/index.js';
2
+ export declare function phaseReducer(phase: Phase, event: InjectEvent): Phase;
@@ -0,0 +1,130 @@
1
+ function phaseReducer(phase, event) {
2
+ switch (event.type) {
3
+ case 'scope-needed': {
4
+ if (phase.kind === 'booting' || phase.kind === 'resolving') {
5
+ return {
6
+ kind: 'scope-select',
7
+ targets: phase.kind === 'resolving' ? phase.targets : [],
8
+ pending: event.pending,
9
+ };
10
+ }
11
+ return phase;
12
+ }
13
+ case 'scope-selected': {
14
+ return phase;
15
+ }
16
+ case 'planning-started': {
17
+ const progress = new Map(event.targets.map((t) => [t.name, { packageName: t.name, status: 'pending' }]));
18
+ return {
19
+ kind: 'planning',
20
+ targets: event.targets,
21
+ scope: event.scope,
22
+ progress,
23
+ };
24
+ }
25
+ case 'plan-step': {
26
+ if (phase.kind !== 'planning')
27
+ return phase;
28
+ const next = new Map(phase.progress);
29
+ next.set(event.step.packageName, event.step);
30
+ return { ...phase, progress: next };
31
+ }
32
+ case 'plans-ready': {
33
+ if (phase.kind !== 'planning')
34
+ return phase;
35
+ return {
36
+ kind: 'diff-review',
37
+ plans: event.plans,
38
+ focusedIndex: 0,
39
+ scope: phase.scope,
40
+ };
41
+ }
42
+ case 'focus-target': {
43
+ if (phase.kind !== 'diff-review')
44
+ return phase;
45
+ return { ...phase, focusedIndex: event.index };
46
+ }
47
+ case 'force-confirm-required': {
48
+ if (phase.kind !== 'diff-review' && phase.kind !== 'applying')
49
+ return phase;
50
+ return {
51
+ kind: 'force-confirm',
52
+ plans: phase.kind === 'diff-review' ? phase.plans : phase.plans,
53
+ warnings: event.warnings,
54
+ pending: event.pending,
55
+ scope: phase.scope,
56
+ };
57
+ }
58
+ case 'force-answer': {
59
+ if (phase.kind !== 'force-confirm')
60
+ return phase;
61
+ if (!event.ok) {
62
+ return {
63
+ kind: 'summary',
64
+ reports: [],
65
+ plans: phase.plans,
66
+ exitCode: 2,
67
+ scope: phase.scope,
68
+ dryRun: false,
69
+ };
70
+ }
71
+ return {
72
+ kind: 'applying',
73
+ plans: phase.plans,
74
+ progress: {
75
+ done: 0,
76
+ total: phase.plans.reduce((acc, tp) => acc + tp.plan.actions.length, 0),
77
+ startedAt: Date.now(),
78
+ },
79
+ scope: phase.scope,
80
+ };
81
+ }
82
+ case 'apply-start': {
83
+ if (phase.kind !== 'diff-review' && phase.kind !== 'force-confirm')
84
+ return phase;
85
+ const plans = phase.kind === 'diff-review' ? phase.plans : phase.plans;
86
+ return {
87
+ kind: 'applying',
88
+ plans,
89
+ progress: { done: 0, total: event.total, startedAt: Date.now() },
90
+ scope: phase.scope,
91
+ };
92
+ }
93
+ case 'apply-progress': {
94
+ if (phase.kind !== 'applying')
95
+ return phase;
96
+ return {
97
+ ...phase,
98
+ progress: {
99
+ ...phase.progress,
100
+ done: event.done,
101
+ current: event.current,
102
+ },
103
+ };
104
+ }
105
+ case 'done': {
106
+ const scope = phase.kind === 'applying' || phase.kind === 'diff-review'
107
+ ? phase.scope
108
+ : 'user';
109
+ const plans = phase.kind === 'applying' || phase.kind === 'diff-review'
110
+ ? phase.plans
111
+ : [];
112
+ return {
113
+ kind: 'summary',
114
+ reports: event.reports,
115
+ plans,
116
+ exitCode: event.exitCode,
117
+ scope,
118
+ dryRun: event.dryRun,
119
+ };
120
+ }
121
+ case 'fail': {
122
+ return { kind: 'error', error: event.error };
123
+ }
124
+ default: {
125
+ return phase;
126
+ }
127
+ }
128
+ }
129
+
130
+ export { phaseReducer };
@@ -0,0 +1,2 @@
1
+ import type { RenderInput } from '../../types/index.js';
2
+ export declare function renderInjectApp(input: RenderInput): Promise<number>;
@@ -0,0 +1,19 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { render } from 'ink';
3
+ import { InjectApp } from '../InjectApp.mjs';
4
+
5
+ async function renderInjectApp(input) {
6
+ let exitCode = 0;
7
+ const instance = render(jsx(InjectApp, { ...input, onExit: (code) => {
8
+ exitCode = code;
9
+ } }), { exitOnCtrlC: true });
10
+ try {
11
+ await instance.waitUntilExit();
12
+ return exitCode;
13
+ }
14
+ finally {
15
+ instance.unmount();
16
+ }
17
+ }
18
+
19
+ export { renderInjectApp };
@@ -0,0 +1,5 @@
1
+ import type { RenderInput } from '../../types/index.js';
2
+ export type { Phase, InjectEvent, RenderInput } from '../../types/index.js';
3
+ export interface InjectAppProps extends RenderInput {
4
+ readonly onExit: (code: 0 | 1 | 2) => void;
5
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { Action } from '../../core/buildPlan/index.js';
3
+ interface ActionRowProps {
4
+ readonly action: Action;
5
+ }
6
+ export declare function ActionRow({ action }: ActionRowProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,45 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { colors } from '../theme/colors.mjs';
4
+ import { icons } from '../theme/icons.mjs';
5
+
6
+ function visualFor(action) {
7
+ switch (action.kind) {
8
+ case 'copy':
9
+ return { icon: icons.plus, color: colors.success };
10
+ case 'skip-uptodate':
11
+ return {
12
+ icon: icons.equals,
13
+ color: colors.muted,
14
+ dim: true,
15
+ note: 'up-to-date',
16
+ };
17
+ case 'warn-diverged':
18
+ return {
19
+ icon: icons.warning,
20
+ color: colors.warn,
21
+ note: 'diverged',
22
+ };
23
+ case 'warn-orphan':
24
+ return {
25
+ icon: icons.question,
26
+ color: colors.warn,
27
+ note: 'orphan',
28
+ };
29
+ case 'delete':
30
+ return {
31
+ icon: icons.minus,
32
+ color: colors.danger,
33
+ note: 'delete',
34
+ };
35
+ default: {
36
+ return { icon: '?', color: colors.muted };
37
+ }
38
+ }
39
+ }
40
+ function ActionRow({ action }) {
41
+ const visual = visualFor(action);
42
+ return (jsxs(Box, { children: [jsxs(Text, { color: visual.color, bold: true, children: [' ', visual.icon, ' '] }), jsx(Text, { color: colors.muted, dimColor: visual.dim, children: action.relPath }), visual.note ? (jsxs(Text, { color: colors.muted, dimColor: true, children: [' ', "(", visual.note, ")"] })) : null] }));
43
+ }
44
+
45
+ export { ActionRow };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface BannerProps {
3
+ readonly version: string;
4
+ readonly scope?: string;
5
+ }
6
+ export declare function Banner({ version, scope, }: BannerProps): React.ReactElement;
7
+ export {};