@forgeax/engine-pack 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +429 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-path.test.d.ts +2 -0
- package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/cli.unit.test.d.ts +2 -0
- package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-asset-config.test.d.ts +2 -0
- package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
- package/dist/__tests__/pack.unit.test.d.ts +2 -0
- package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts +2 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/runtime.browser.test.d.ts +2 -0
- package/dist/__tests__/runtime.browser.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology.unit.test.d.ts +2 -0
- package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
- package/dist/artifact-path.d.ts +9 -0
- package/dist/artifact-path.d.ts.map +1 -0
- package/dist/artifact-path.mjs +53 -0
- package/dist/artifact-path.mjs.map +1 -0
- package/dist/atlas/run-atlas.d.ts +8 -0
- package/dist/atlas/run-atlas.d.ts.map +1 -0
- package/dist/atlas/shelf-pack.d.ts +55 -0
- package/dist/atlas/shelf-pack.d.ts.map +1 -0
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.mjs +3 -0
- package/dist/bridge.mjs.map +1 -0
- package/dist/build.d.ts +19 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.mjs +10203 -0
- package/dist/build.mjs.map +1 -0
- package/dist/builtin.d.ts +44 -0
- package/dist/builtin.d.ts.map +1 -0
- package/dist/builtin.mjs +141 -0
- package/dist/builtin.mjs.map +1 -0
- package/dist/catalog-builder.d.ts +43 -0
- package/dist/catalog-builder.d.ts.map +1 -0
- package/dist/catalog-delta.d.ts +11 -0
- package/dist/catalog-delta.d.ts.map +1 -0
- package/dist/catalog-projection.d.ts +91 -0
- package/dist/catalog-projection.d.ts.map +1 -0
- package/dist/cli-asset.d.ts +22 -0
- package/dist/cli-asset.d.ts.map +1 -0
- package/dist/cli-asset.mjs +9697 -0
- package/dist/cli-asset.mjs.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.mjs +29 -0
- package/dist/config.mjs.map +1 -0
- package/dist/deriveAssetName.d.ts +15 -0
- package/dist/deriveAssetName.d.ts.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +19 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/evidence/material-cook.d.ts +92 -0
- package/dist/evidence/material-cook.d.ts.map +1 -0
- package/dist/evidence/offline-evidence.d.ts +25 -0
- package/dist/evidence/offline-evidence.d.ts.map +1 -0
- package/dist/evidence/source-inventory.d.ts +29 -0
- package/dist/evidence/source-inventory.d.ts.map +1 -0
- package/dist/guid.d.ts +45 -0
- package/dist/guid.d.ts.map +1 -0
- package/dist/guid.mjs +94 -0
- package/dist/guid.mjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8612 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-writer.d.ts +14 -0
- package/dist/material/artifact-writer.d.ts.map +1 -0
- package/dist/material-cook.d.ts +2 -0
- package/dist/material-cook.d.ts.map +1 -0
- package/dist/material-cook.mjs +248 -0
- package/dist/material-cook.mjs.map +1 -0
- package/dist/mesh-bin-contract.d.ts +36 -0
- package/dist/mesh-bin-contract.d.ts.map +1 -0
- package/dist/mesh-bin-contract.mjs +96 -0
- package/dist/mesh-bin-contract.mjs.map +1 -0
- package/dist/name.mjs +22 -0
- package/dist/name.mjs.map +1 -0
- package/dist/native-cooker-registry.d.ts +47 -0
- package/dist/native-cooker-registry.d.ts.map +1 -0
- package/dist/native-cooker.d.ts +3 -0
- package/dist/native-cooker.d.ts.map +1 -0
- package/dist/native-cooker.mjs +142 -0
- package/dist/native-cooker.mjs.map +1 -0
- package/dist/package-finalizer.d.ts +121 -0
- package/dist/package-finalizer.d.ts.map +1 -0
- package/dist/producer-contract.d.ts +19 -0
- package/dist/producer-contract.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts +4 -0
- package/dist/resolve-asset-source.d.ts.map +1 -0
- package/dist/resolve-asset-source.mjs +109 -0
- package/dist/resolve-asset-source.mjs.map +1 -0
- package/dist/runtime-projection.d.ts +14 -0
- package/dist/runtime-projection.d.ts.map +1 -0
- package/dist/runtime-publication.d.ts +44 -0
- package/dist/runtime-publication.d.ts.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.mjs +7261 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/scanner.d.ts +113 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.mjs +8819 -0
- package/dist/scanner.mjs.map +1 -0
- package/dist/schema-compiled.d.ts +12 -0
- package/dist/schema-compiled.d.ts.map +1 -0
- package/dist/schema.mjs +7369 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scriptable-pack-node.d.ts +62 -0
- package/dist/scriptable-pack-node.d.ts.map +1 -0
- package/dist/scriptable-pack-node.mjs +1674 -0
- package/dist/scriptable-pack-node.mjs.map +1 -0
- package/dist/scriptable-pack-worker.d.ts +2 -0
- package/dist/scriptable-pack-worker.d.ts.map +1 -0
- package/dist/scriptable-pack-worker.mjs +224 -0
- package/dist/scriptable-pack-worker.mjs.map +1 -0
- package/dist/scriptable-pack.d.ts +558 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack.mjs +672 -0
- package/dist/scriptable-pack.mjs.map +1 -0
- package/dist/topology.d.ts +10 -0
- package/dist/topology.d.ts.map +1 -0
- package/package.json +154 -0
- package/schema/meta.schema.json +173 -0
- package/schema/pack.schema.json +232 -0
- package/src/__tests__/ai-recovery.unit.test.ts +114 -0
- package/src/__tests__/artifact-path.test.ts +41 -0
- package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
- package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
- package/src/__tests__/asset-evidence-schema.test.ts +86 -0
- package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
- package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
- package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
- package/src/__tests__/cli.unit.test.ts +448 -0
- package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
- package/src/__tests__/guid-collision.unit.test.ts +112 -0
- package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
- package/src/__tests__/load-asset-config.test.ts +121 -0
- package/src/__tests__/material-cook-dependencies.unit.test.ts +112 -0
- package/src/__tests__/material-cook-receipt.unit.test.ts +148 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +152 -0
- package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +22 -0
- package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
- package/src/__tests__/meta-schema-optional.test.ts +116 -0
- package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
- package/src/__tests__/native-cooker-registry.test.ts +64 -0
- package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
- package/src/__tests__/offline-evidence.integration.test.ts +106 -0
- package/src/__tests__/owner-chain.integration.test.ts +78 -0
- package/src/__tests__/pack-v2-fixture-migration.test.ts +87 -0
- package/src/__tests__/pack-v2-schema.test.ts +142 -0
- package/src/__tests__/pack.unit.test.ts +2218 -0
- package/src/__tests__/package-finalizer.contract.test.ts +105 -0
- package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
- package/src/__tests__/resolve-asset-source.test.ts +121 -0
- package/src/__tests__/runtime-projection.unit.test.ts +87 -0
- package/src/__tests__/runtime.browser.test.ts +15 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +167 -0
- package/src/__tests__/scanner-inventory.test.ts +76 -0
- package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
- package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
- package/src/__tests__/scriptable-pack.test-d.ts +131 -0
- package/src/__tests__/scriptable-pack.unit.test.ts +789 -0
- package/src/__tests__/topology-actions.unit.test.ts +94 -0
- package/src/__tests__/topology.unit.test.ts +85 -0
- package/src/artifact-path.ts +69 -0
- package/src/atlas/run-atlas.ts +358 -0
- package/src/atlas/shelf-pack.ts +172 -0
- package/src/atlas/upng-ambient.d.ts +13 -0
- package/src/bridge.ts +2 -0
- package/src/build.ts +139 -0
- package/src/builtin.ts +117 -0
- package/src/catalog-builder.ts +313 -0
- package/src/catalog-delta.ts +189 -0
- package/src/catalog-projection.ts +344 -0
- package/src/cli-asset.ts +586 -0
- package/src/config.ts +36 -0
- package/src/deriveAssetName.ts +42 -0
- package/src/errors.ts +47 -0
- package/src/evidence/material-cook.ts +411 -0
- package/src/evidence/offline-evidence.ts +90 -0
- package/src/evidence/source-inventory.ts +193 -0
- package/src/guid.ts +99 -0
- package/src/index.ts +108 -0
- package/src/material/artifact-writer.ts +26 -0
- package/src/material-cook.ts +15 -0
- package/src/mesh-bin-contract.ts +150 -0
- package/src/native-cooker-registry.ts +235 -0
- package/src/native-cooker.ts +2 -0
- package/src/package-finalizer.ts +394 -0
- package/src/producer-contract.ts +277 -0
- package/src/resolve-asset-source.ts +88 -0
- package/src/runtime-projection.ts +100 -0
- package/src/runtime-publication.ts +190 -0
- package/src/runtime.ts +23 -0
- package/src/scanner.ts +715 -0
- package/src/schema/material-cook.schema.json +119 -0
- package/src/schema-compiled.ts +238 -0
- package/src/scriptable-pack-node.ts +1394 -0
- package/src/scriptable-pack-worker.ts +308 -0
- package/src/scriptable-pack.ts +1006 -0
- package/src/topology.ts +157 -0
package/src/cli-asset.ts
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// @forgeax/engine-pack/src/cli-asset — `forgeax-engine-remote-asset`
|
|
4
|
+
// plugin bin (feat-20260516-console-dependency-inversion plan-strategy
|
|
5
|
+
// section 2.9). Discovered by the base bin via the kubectl 4th-path
|
|
6
|
+
// `forgeax-engine-remote-` prefix scanner; subcommands scan / lookup /
|
|
7
|
+
// verify operate offline against the pack scanner.
|
|
8
|
+
//
|
|
9
|
+
// stderr contract (plan-strategy section 2.3 weak-contract): every error
|
|
10
|
+
// path emits a single JSON Lines record carrying `code` / `expected` /
|
|
11
|
+
// `hint`; `detail` is included when the underlying error supplied one.
|
|
12
|
+
// Exit codes: 0 success, 1 any error.
|
|
13
|
+
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
16
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { parseArgs } from 'node:util';
|
|
19
|
+
import type { ArtifactDescriptor, AssetEvidence, CookReceipt } from '@forgeax/engine-types';
|
|
20
|
+
import { validateArtifactPath } from './artifact-path.js';
|
|
21
|
+
import { runAtlas } from './atlas/run-atlas.js';
|
|
22
|
+
import {
|
|
23
|
+
buildOfflineAssetEvidence,
|
|
24
|
+
type OfflineArtifactInput,
|
|
25
|
+
} from './evidence/offline-evidence.js';
|
|
26
|
+
import { readSourceInventory } from './evidence/source-inventory.js';
|
|
27
|
+
import { isValidAssetGuidString } from './guid.js';
|
|
28
|
+
import { parsePackV2 } from './index.js';
|
|
29
|
+
import { scan } from './scanner.js';
|
|
30
|
+
import { isRecord, projectScriptablePackMeta } from './scriptable-pack.js';
|
|
31
|
+
import { loadScriptablePack } from './scriptable-pack-node.js';
|
|
32
|
+
|
|
33
|
+
export interface PackEntry {
|
|
34
|
+
readonly guid: string;
|
|
35
|
+
readonly kind: string;
|
|
36
|
+
readonly sourcePath: string;
|
|
37
|
+
readonly name?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface AssetCtx {
|
|
41
|
+
readonly stdoutWrite: (line: string) => void;
|
|
42
|
+
readonly stderrWrite: (line: string) => void;
|
|
43
|
+
/** Optional cwd override (defaults to `process.cwd()`); enables hermetic tests. */
|
|
44
|
+
readonly cwd?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface ScanErrShape {
|
|
48
|
+
code: string;
|
|
49
|
+
expected: string;
|
|
50
|
+
hint: string;
|
|
51
|
+
detail?: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function emitError(ctx: AssetCtx, err: ScanErrShape): number {
|
|
55
|
+
const payload: Record<string, unknown> = {
|
|
56
|
+
code: err.code,
|
|
57
|
+
expected: err.expected,
|
|
58
|
+
hint: err.hint,
|
|
59
|
+
};
|
|
60
|
+
if (err.detail !== undefined) payload.detail = err.detail;
|
|
61
|
+
ctx.stderrWrite(JSON.stringify(payload));
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function scanEntries(
|
|
66
|
+
roots: readonly string[],
|
|
67
|
+
ctx: AssetCtx,
|
|
68
|
+
): Promise<{ ok: true; value: PackEntry[] } | { ok: false }> {
|
|
69
|
+
const result = await scan(roots);
|
|
70
|
+
if (!result.ok) {
|
|
71
|
+
emitError(ctx, {
|
|
72
|
+
code: result.error.code,
|
|
73
|
+
expected: result.error.expected,
|
|
74
|
+
hint: result.error.hint,
|
|
75
|
+
detail: result.error.detail,
|
|
76
|
+
});
|
|
77
|
+
return { ok: false };
|
|
78
|
+
}
|
|
79
|
+
const entries: PackEntry[] = [];
|
|
80
|
+
// Two file kinds carry GUID-addressed entries (disk schema SSOT, AGENTS.md
|
|
81
|
+
// §Disk schema): `.pack.json` (`internal-text-package`) holds top-level
|
|
82
|
+
// `assets[]`, while every `*.meta.json` sidecar (including
|
|
83
|
+
// any `*.meta.json` regardless of source extension (top-level `importer`), all of
|
|
84
|
+
// kind `external-asset-package`) holds `subAssets[]`. The CLI surface
|
|
85
|
+
// must enumerate both so that `scan` / `lookup` mirror what the
|
|
86
|
+
// build-time catalog builder folds into `pack-index.json` (otherwise
|
|
87
|
+
// the same disk schema yields different entity counts on the two
|
|
88
|
+
// surfaces — observed regression in feat-20260517 sandbox T-2.B1).
|
|
89
|
+
const packPaths = result.value.filter((p) => p.endsWith('.pack.json'));
|
|
90
|
+
const metaPaths = result.value.filter((p) => p.endsWith('.meta.json'));
|
|
91
|
+
for (const packPath of packPaths) {
|
|
92
|
+
let parsed: unknown;
|
|
93
|
+
try {
|
|
94
|
+
const raw = await readFile(packPath, 'utf-8');
|
|
95
|
+
parsed = JSON.parse(raw);
|
|
96
|
+
} catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const packObj = parsed as { assets?: { guid?: unknown; kind?: unknown; name?: unknown }[] };
|
|
100
|
+
if (!Array.isArray(packObj.assets)) continue;
|
|
101
|
+
for (const asset of packObj.assets) {
|
|
102
|
+
if (typeof asset.guid === 'string' && typeof asset.kind === 'string') {
|
|
103
|
+
entries.push({
|
|
104
|
+
guid: asset.guid,
|
|
105
|
+
kind: asset.kind,
|
|
106
|
+
sourcePath: packPath,
|
|
107
|
+
...(typeof asset.name === 'string' ? { name: asset.name } : {}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const metaPath of metaPaths) {
|
|
113
|
+
let parsed: unknown;
|
|
114
|
+
try {
|
|
115
|
+
const raw = await readFile(metaPath, 'utf-8');
|
|
116
|
+
parsed = JSON.parse(raw);
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const metaObj = parsed as {
|
|
121
|
+
subAssets?: { guid?: unknown; kind?: unknown; name?: unknown; sourceKey?: unknown }[];
|
|
122
|
+
};
|
|
123
|
+
if (!Array.isArray(metaObj.subAssets)) continue;
|
|
124
|
+
for (const sub of metaObj.subAssets) {
|
|
125
|
+
if (typeof sub.guid === 'string' && typeof sub.kind === 'string') {
|
|
126
|
+
const name =
|
|
127
|
+
typeof sub.name === 'string'
|
|
128
|
+
? sub.name
|
|
129
|
+
: typeof sub.sourceKey === 'string'
|
|
130
|
+
? sub.sourceKey
|
|
131
|
+
: undefined;
|
|
132
|
+
entries.push({
|
|
133
|
+
guid: sub.guid,
|
|
134
|
+
kind: sub.kind,
|
|
135
|
+
sourcePath: metaPath,
|
|
136
|
+
...(name === undefined ? {} : { name }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const sourcePath of result.value.filter((path) => path.endsWith('.pack.ts'))) {
|
|
142
|
+
const loaded = await loadScriptablePack(sourcePath, { metadataOnly: true });
|
|
143
|
+
if (!loaded.ok) {
|
|
144
|
+
emitError(ctx, loaded.error);
|
|
145
|
+
return { ok: false };
|
|
146
|
+
}
|
|
147
|
+
const meta = projectScriptablePackMeta(loaded.value, sourcePath);
|
|
148
|
+
for (const sub of meta.subAssets) {
|
|
149
|
+
entries.push({ guid: sub.guid, kind: sub.kind, sourcePath });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const firstByGuid = new Map<string, PackEntry>();
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
const guid = entry.guid.toLowerCase();
|
|
155
|
+
const first = firstByGuid.get(guid);
|
|
156
|
+
if (first !== undefined) {
|
|
157
|
+
emitError(ctx, {
|
|
158
|
+
code: 'pack-guid-collision',
|
|
159
|
+
expected:
|
|
160
|
+
'one author declaration per GUID across Meta, Pack v2, and ScriptablePack sources',
|
|
161
|
+
hint: 'allocate a new durable GUID through the asset authoring gateway, then rerun verify',
|
|
162
|
+
detail: { guid, paths: [first.sourcePath, entry.sourcePath] },
|
|
163
|
+
});
|
|
164
|
+
return { ok: false };
|
|
165
|
+
}
|
|
166
|
+
firstByGuid.set(guid, entry);
|
|
167
|
+
}
|
|
168
|
+
return { ok: true, value: entries };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function helpBody(): string {
|
|
172
|
+
return [
|
|
173
|
+
'forgeax-engine-remote-asset — offline pack scanner / lookup / verifier / atlas builder',
|
|
174
|
+
'',
|
|
175
|
+
'Usage:',
|
|
176
|
+
' forgeax-engine-remote-asset scan [--roots <dir>]',
|
|
177
|
+
' forgeax-engine-remote-asset lookup <guid>',
|
|
178
|
+
' forgeax-engine-remote-asset verify',
|
|
179
|
+
' forgeax-engine-remote-asset meta <source.pack.ts> --json',
|
|
180
|
+
' forgeax-engine-remote-asset lookup --guid <guid> --project <dir> --catalog <path> --json',
|
|
181
|
+
' forgeax-engine-remote-asset verify --guid <guid> --project <dir> --catalog <path> --json',
|
|
182
|
+
' forgeax-engine-remote-asset atlas --input <glob> --name <prefix> [--output <dir>] [--max-atlas-size <n>]',
|
|
183
|
+
'',
|
|
184
|
+
'AI recovery commands (host execution remains explicit):',
|
|
185
|
+
' inspect [--guid <guid>] read subject, execution, lifecycle, authority, and sourceKey evidence',
|
|
186
|
+
' rebuild [--guid <guid>] request the declared producer to rebuild',
|
|
187
|
+
' cold-cook [--guid <guid>] discard invalid DDC output and cook from author authority',
|
|
188
|
+
' preview-LKG [--guid <guid>] read the explicit last-known-good projection',
|
|
189
|
+
' override [--guid <guid>] request an Editor-owned override through its gateway',
|
|
190
|
+
' promote [--guid <guid>] request an authored Pack from an imported result',
|
|
191
|
+
' stop-publish [--guid <guid>] block publication while evidence is incomplete',
|
|
192
|
+
'',
|
|
193
|
+
].join('\n');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function runCliAsset(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
197
|
+
const [sub, ...subRest] = rest;
|
|
198
|
+
if (sub === undefined || sub === '--help' || sub === '-h') {
|
|
199
|
+
ctx.stdoutWrite(helpBody());
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
switch (sub) {
|
|
203
|
+
case 'scan':
|
|
204
|
+
return runScan(subRest, ctx);
|
|
205
|
+
case 'lookup':
|
|
206
|
+
return runLookup(subRest, ctx);
|
|
207
|
+
case 'verify':
|
|
208
|
+
return runVerify(subRest, ctx);
|
|
209
|
+
case 'atlas':
|
|
210
|
+
return runAtlas(subRest, ctx);
|
|
211
|
+
case 'meta':
|
|
212
|
+
return runMeta(subRest, ctx);
|
|
213
|
+
case 'inspect':
|
|
214
|
+
case 'rebuild':
|
|
215
|
+
case 'cold-cook':
|
|
216
|
+
case 'preview-LKG':
|
|
217
|
+
case 'override':
|
|
218
|
+
case 'promote':
|
|
219
|
+
case 'stop-publish':
|
|
220
|
+
return runRecoveryCommand(sub, subRest, ctx);
|
|
221
|
+
default:
|
|
222
|
+
return emitError(ctx, {
|
|
223
|
+
code: 'unknown-subcommand',
|
|
224
|
+
expected: 'subcommand in {scan, lookup, verify, atlas, meta}',
|
|
225
|
+
hint: "run 'forgeax-engine-remote-asset --help' for usage",
|
|
226
|
+
detail: { subcommand: sub },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function runMeta(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
232
|
+
let source: string | undefined;
|
|
233
|
+
try {
|
|
234
|
+
const parsed = parseArgs({
|
|
235
|
+
args: rest,
|
|
236
|
+
allowPositionals: true,
|
|
237
|
+
strict: true,
|
|
238
|
+
options: { json: { type: 'boolean' } },
|
|
239
|
+
});
|
|
240
|
+
source = parsed.positionals[0];
|
|
241
|
+
if (source === undefined || parsed.positionals.length !== 1) {
|
|
242
|
+
throw new Error('exactly one ScriptablePack source path is required');
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
return emitError(ctx, {
|
|
246
|
+
code: 'cli-parse-error',
|
|
247
|
+
expected: 'forgeax-engine-remote-asset meta <source.pack.ts> --json',
|
|
248
|
+
hint: 'pass one trusted ScriptablePack module path',
|
|
249
|
+
detail: { message: error instanceof Error ? error.message : String(error) },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
253
|
+
const loaded = await loadScriptablePack(absolutePath(source, cwd), { metadataOnly: true });
|
|
254
|
+
if (!loaded.ok) return emitError(ctx, loaded.error);
|
|
255
|
+
ctx.stdoutWrite(JSON.stringify(projectScriptablePackMeta(loaded.value, source)));
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Emit a canonical host-operation request so recovery actions are executable
|
|
260
|
+
* from one CLI door even when the actual write/cook owner is the running host. */
|
|
261
|
+
async function runRecoveryCommand(
|
|
262
|
+
operation:
|
|
263
|
+
| 'inspect'
|
|
264
|
+
| 'rebuild'
|
|
265
|
+
| 'cold-cook'
|
|
266
|
+
| 'preview-LKG'
|
|
267
|
+
| 'override'
|
|
268
|
+
| 'promote'
|
|
269
|
+
| 'stop-publish',
|
|
270
|
+
rest: string[],
|
|
271
|
+
ctx: AssetCtx,
|
|
272
|
+
): Promise<number> {
|
|
273
|
+
try {
|
|
274
|
+
const parsed = parseArgs({
|
|
275
|
+
args: rest,
|
|
276
|
+
allowPositionals: false,
|
|
277
|
+
strict: true,
|
|
278
|
+
options: { guid: { type: 'string' } },
|
|
279
|
+
});
|
|
280
|
+
const guid = parsed.values.guid;
|
|
281
|
+
ctx.stdoutWrite(
|
|
282
|
+
JSON.stringify({
|
|
283
|
+
operation,
|
|
284
|
+
status: 'accepted',
|
|
285
|
+
execution: 'host-gateway',
|
|
286
|
+
...(typeof guid === 'string' ? { guid } : {}),
|
|
287
|
+
hint: 'forward this operation descriptor to the running Editor/Studio gateway for its terminal result',
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
return 0;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
return emitError(ctx, {
|
|
293
|
+
code: 'cli-parse-error',
|
|
294
|
+
expected: `${operation} [--guid <guid>]`,
|
|
295
|
+
hint: "run 'forgeax-engine-remote-asset --help' for usage",
|
|
296
|
+
detail: { message: error instanceof Error ? error.message : String(error) },
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function runScan(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
302
|
+
let roots: string[];
|
|
303
|
+
try {
|
|
304
|
+
const parsed = parseArgs({
|
|
305
|
+
args: rest,
|
|
306
|
+
allowPositionals: false,
|
|
307
|
+
strict: true,
|
|
308
|
+
options: { roots: { type: 'string', multiple: true } },
|
|
309
|
+
});
|
|
310
|
+
roots = (parsed.values.roots as string[] | undefined) ?? [ctx.cwd ?? process.cwd()];
|
|
311
|
+
} catch (e) {
|
|
312
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
313
|
+
return emitError(ctx, {
|
|
314
|
+
code: 'cli-parse-error',
|
|
315
|
+
expected: 'forgeax-engine-remote-asset scan [--roots <dir>]',
|
|
316
|
+
hint: "run 'forgeax-engine-remote-asset --help' for usage",
|
|
317
|
+
detail: { message },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const result = await scanEntries(roots, ctx);
|
|
321
|
+
if (!result.ok) return 1;
|
|
322
|
+
ctx.stdoutWrite(JSON.stringify(result.value));
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function runLookup(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
327
|
+
if (rest.some((value) => value === '--guid')) return runEvidence(rest, ctx);
|
|
328
|
+
const [guid] = rest;
|
|
329
|
+
if (typeof guid !== 'string') {
|
|
330
|
+
return emitError(ctx, {
|
|
331
|
+
code: 'cli-parse-error',
|
|
332
|
+
expected: 'forgeax-engine-remote-asset lookup <36-char-uuid>',
|
|
333
|
+
hint: 'pass a 36-char dash-form UUID positional argument',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (!isValidAssetGuidString(guid)) {
|
|
337
|
+
return emitError(ctx, {
|
|
338
|
+
code: 'pack-guid-malformed',
|
|
339
|
+
expected: '36-char RFC 4122 dash-form GUID (8-4-4-4-12 lowercase hex)',
|
|
340
|
+
hint: 'use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form',
|
|
341
|
+
detail: { raw: guid, reason: 'invalid-format' },
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
345
|
+
const result = await scanEntries([cwd], ctx);
|
|
346
|
+
if (!result.ok) return 1;
|
|
347
|
+
const normalized = guid.toLowerCase();
|
|
348
|
+
const entry = result.value.find((e) => e.guid.toLowerCase() === normalized);
|
|
349
|
+
if (entry === undefined) {
|
|
350
|
+
return emitError(ctx, {
|
|
351
|
+
code: 'asset-not-found',
|
|
352
|
+
expected: 'GUID present in scan results',
|
|
353
|
+
hint: 'run scan to list known GUIDs; verify the GUID came from a .meta.json sub-asset entry',
|
|
354
|
+
detail: { guid: normalized },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
ctx.stdoutWrite(JSON.stringify(entry));
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function runVerify(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
362
|
+
if (rest.some((value) => value === '--guid')) return runEvidence(rest, ctx);
|
|
363
|
+
try {
|
|
364
|
+
parseArgs({
|
|
365
|
+
args: rest,
|
|
366
|
+
allowPositionals: false,
|
|
367
|
+
strict: true,
|
|
368
|
+
options: {},
|
|
369
|
+
});
|
|
370
|
+
} catch (e) {
|
|
371
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
372
|
+
return emitError(ctx, {
|
|
373
|
+
code: 'cli-parse-error',
|
|
374
|
+
expected: 'forgeax-engine-remote-asset verify',
|
|
375
|
+
hint: "run 'forgeax-engine-remote-asset --help' for usage",
|
|
376
|
+
detail: { message },
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
380
|
+
const result = await scanEntries([cwd], ctx);
|
|
381
|
+
if (!result.ok) return 1;
|
|
382
|
+
|
|
383
|
+
const materialCount = result.value.filter((e) => e.kind === 'material').length;
|
|
384
|
+
ctx.stdoutWrite(`material-validated: ${materialCount}`);
|
|
385
|
+
return 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
interface EvidenceCliOptions {
|
|
389
|
+
readonly guid: string;
|
|
390
|
+
readonly project: string;
|
|
391
|
+
readonly catalog: string;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Parse the machine-oriented evidence probe flags shared by lookup and verify. */
|
|
395
|
+
function parseEvidenceOptions(rest: string[], ctx: AssetCtx): EvidenceCliOptions | undefined {
|
|
396
|
+
try {
|
|
397
|
+
const parsed = parseArgs({
|
|
398
|
+
args: rest,
|
|
399
|
+
allowPositionals: false,
|
|
400
|
+
strict: true,
|
|
401
|
+
options: {
|
|
402
|
+
guid: { type: 'string' },
|
|
403
|
+
project: { type: 'string' },
|
|
404
|
+
catalog: { type: 'string' },
|
|
405
|
+
json: { type: 'boolean' },
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
const guid = parsed.values.guid;
|
|
409
|
+
const project = parsed.values.project;
|
|
410
|
+
const catalog = parsed.values.catalog;
|
|
411
|
+
if (typeof guid !== 'string' || typeof project !== 'string' || typeof catalog !== 'string') {
|
|
412
|
+
emitError(ctx, {
|
|
413
|
+
code: 'cli-parse-error',
|
|
414
|
+
expected: '--guid <guid> --project <dir> --catalog <path> --json',
|
|
415
|
+
hint: "run 'forgeax-engine-remote-asset --help' for usage",
|
|
416
|
+
});
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
return { guid, project, catalog };
|
|
420
|
+
} catch (e) {
|
|
421
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
422
|
+
emitError(ctx, {
|
|
423
|
+
code: 'cli-parse-error',
|
|
424
|
+
expected: '--guid <guid> --project <dir> --catalog <path> --json',
|
|
425
|
+
hint: "run 'forgeax-engine-remote-asset --help' for usage",
|
|
426
|
+
detail: { message },
|
|
427
|
+
});
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function absolutePath(path: string, cwd: string): string {
|
|
433
|
+
return isAbsolute(path) ? path : resolve(cwd, path);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function readJson(path: string, ctx: AssetCtx): Promise<unknown | undefined> {
|
|
437
|
+
try {
|
|
438
|
+
return JSON.parse(await readFile(path, 'utf8')) as unknown;
|
|
439
|
+
} catch (e) {
|
|
440
|
+
emitError(ctx, {
|
|
441
|
+
code: 'asset-evidence-input-invalid',
|
|
442
|
+
expected: `readable JSON evidence input at ${path}`,
|
|
443
|
+
hint: 'restore the catalog or cook receipt JSON, then rerun lookup or verify',
|
|
444
|
+
detail: { path, message: e instanceof Error ? e.message : String(e) },
|
|
445
|
+
});
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function catalogRows(value: unknown): readonly Record<string, unknown>[] | undefined {
|
|
451
|
+
if (Array.isArray(value)) return value.filter(isRecord);
|
|
452
|
+
if (!isRecord(value) || !Array.isArray(value.entries)) return undefined;
|
|
453
|
+
return value.entries.filter(isRecord);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function packagePath(projectRoot: string, packageUrl: string): string {
|
|
457
|
+
const relative = packageUrl.replace(/^\/+/, '');
|
|
458
|
+
return resolve(projectRoot, relative);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function artifactInputs(
|
|
462
|
+
pack: { readonly guid: string; readonly artifacts: Readonly<Record<string, ArtifactDescriptor>> },
|
|
463
|
+
packagePathValue: string,
|
|
464
|
+
): Promise<Readonly<Record<string, OfflineArtifactInput>>> {
|
|
465
|
+
return Promise.all(
|
|
466
|
+
Object.entries(pack.artifacts).map(async ([key, descriptor]) => {
|
|
467
|
+
const pathResult = validateArtifactPath(descriptor.path, {
|
|
468
|
+
packageRoot: dirname(packagePathValue),
|
|
469
|
+
guid: pack.guid,
|
|
470
|
+
artifactKey: key,
|
|
471
|
+
});
|
|
472
|
+
if (!pathResult.ok) return [key, { ...descriptor, verification: 'failed' as const }] as const;
|
|
473
|
+
const artifactPath = resolve(dirname(packagePathValue), pathResult.value);
|
|
474
|
+
try {
|
|
475
|
+
const bytes = await readFile(artifactPath);
|
|
476
|
+
const lengthOk =
|
|
477
|
+
descriptor.byteLength === undefined || descriptor.byteLength === bytes.byteLength;
|
|
478
|
+
const digestOk =
|
|
479
|
+
descriptor.integrity === undefined ||
|
|
480
|
+
createHash('sha256').update(bytes).digest('base64') === descriptor.integrity.digest;
|
|
481
|
+
return [
|
|
482
|
+
key,
|
|
483
|
+
{
|
|
484
|
+
...descriptor,
|
|
485
|
+
verification: lengthOk && digestOk ? ('passed' as const) : ('failed' as const),
|
|
486
|
+
},
|
|
487
|
+
] as const;
|
|
488
|
+
} catch {
|
|
489
|
+
return [key, { ...descriptor, verification: 'failed' as const }] as const;
|
|
490
|
+
}
|
|
491
|
+
}),
|
|
492
|
+
).then((entries) => Object.fromEntries(entries));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Join offline source, catalog, receipt, and Pack v2 facts into one JSON result. */
|
|
496
|
+
async function runEvidence(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
497
|
+
const options = parseEvidenceOptions(rest, ctx);
|
|
498
|
+
if (options === undefined) return 1;
|
|
499
|
+
if (!isValidAssetGuidString(options.guid)) {
|
|
500
|
+
return emitError(ctx, {
|
|
501
|
+
code: 'pack-guid-malformed',
|
|
502
|
+
expected: '36-char RFC 4122 dash-form GUID (8-4-4-4-12 lowercase hex)',
|
|
503
|
+
hint: 'use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form',
|
|
504
|
+
detail: { raw: options.guid, reason: 'invalid-format' },
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const projectRoot = absolutePath(options.project, ctx.cwd ?? process.cwd());
|
|
508
|
+
const catalogValue = await readJson(absolutePath(options.catalog, ctx.cwd ?? process.cwd()), ctx);
|
|
509
|
+
if (catalogValue === undefined) return 1;
|
|
510
|
+
const rows = catalogRows(catalogValue);
|
|
511
|
+
if (rows === undefined) {
|
|
512
|
+
return emitError(ctx, {
|
|
513
|
+
code: 'asset-evidence-input-invalid',
|
|
514
|
+
expected: 'catalog JSON array or {entries: []} object',
|
|
515
|
+
hint: 'rebuild the catalog and rerun lookup or verify',
|
|
516
|
+
detail: { path: options.catalog },
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
const row = rows.find(
|
|
520
|
+
(candidate) => candidate.guid?.toString().toLowerCase() === options.guid.toLowerCase(),
|
|
521
|
+
);
|
|
522
|
+
if (row === undefined || typeof row.packageUrl !== 'string') {
|
|
523
|
+
return emitError(ctx, {
|
|
524
|
+
code: 'asset-not-found',
|
|
525
|
+
expected: 'catalog row with GUID and packageUrl',
|
|
526
|
+
hint: 'rebuild the catalog or verify the GUID came from the project source inventory',
|
|
527
|
+
detail: { guid: options.guid },
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
const packageUrl = row.packageUrl;
|
|
531
|
+
const packageFile = packagePath(projectRoot, packageUrl);
|
|
532
|
+
const packageValue = await readJson(packageFile, ctx);
|
|
533
|
+
if (packageValue === undefined) return 1;
|
|
534
|
+
const parsedPack = parsePackV2(packageValue);
|
|
535
|
+
if (!parsedPack.ok) return emitError(ctx, parsedPack.error);
|
|
536
|
+
const asset = parsedPack.value.assets.find(
|
|
537
|
+
(candidate) => candidate.guid.toLowerCase() === options.guid.toLowerCase(),
|
|
538
|
+
);
|
|
539
|
+
if (asset === undefined) {
|
|
540
|
+
return emitError(ctx, {
|
|
541
|
+
code: 'asset-evidence-input-invalid',
|
|
542
|
+
expected: 'Pack v2 package asset with the requested GUID',
|
|
543
|
+
hint: 'recook the package and rebuild the catalog so the GUID and package agree',
|
|
544
|
+
detail: { guid: options.guid, packageUrl },
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
const source = await readSourceInventory({ projectRoot, guid: options.guid });
|
|
548
|
+
const receipt =
|
|
549
|
+
typeof row.cookReceiptUrl === 'string'
|
|
550
|
+
? await readJson(packagePath(projectRoot, row.cookReceiptUrl), ctx)
|
|
551
|
+
: undefined;
|
|
552
|
+
if (row.cookReceiptUrl !== undefined && receipt === undefined) return 1;
|
|
553
|
+
const packageArtifacts = await artifactInputs(asset, packageFile);
|
|
554
|
+
const result = await buildOfflineAssetEvidence({
|
|
555
|
+
guid: options.guid,
|
|
556
|
+
...(source === undefined ? {} : { source }),
|
|
557
|
+
locator: {
|
|
558
|
+
packageUrl,
|
|
559
|
+
...(typeof row.cookReceiptUrl === 'string' ? { cookReceiptUrl: row.cookReceiptUrl } : {}),
|
|
560
|
+
},
|
|
561
|
+
...(receipt !== undefined ? { receipt: receipt as CookReceipt } : {}),
|
|
562
|
+
package: { guid: asset.guid, artifacts: packageArtifacts },
|
|
563
|
+
});
|
|
564
|
+
if (!result.ok) return emitError(ctx, result.error);
|
|
565
|
+
ctx.stdoutWrite(JSON.stringify(result.value satisfies AssetEvidence));
|
|
566
|
+
return 0;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Bin entry guard — only fires when this module is the process entry.
|
|
570
|
+
const isBinEntry = await (async (): Promise<boolean> => {
|
|
571
|
+
const argv1 = process.argv[1];
|
|
572
|
+
if (typeof argv1 !== 'string') return false;
|
|
573
|
+
const argv1Real = await realpath(argv1).catch(() => argv1);
|
|
574
|
+
const selfReal = await realpath(fileURLToPath(import.meta.url)).catch(() =>
|
|
575
|
+
fileURLToPath(import.meta.url),
|
|
576
|
+
);
|
|
577
|
+
return argv1Real === selfReal;
|
|
578
|
+
})();
|
|
579
|
+
|
|
580
|
+
if (isBinEntry) {
|
|
581
|
+
const exitCode = await runCliAsset(process.argv.slice(2), {
|
|
582
|
+
stdoutWrite: (line: string) => process.stdout.write(`${line}\n`),
|
|
583
|
+
stderrWrite: (line: string) => process.stderr.write(`${line}\n`),
|
|
584
|
+
});
|
|
585
|
+
process.exit(exitCode);
|
|
586
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export interface LoadAssetConfigResult {
|
|
5
|
+
readonly roots: readonly string[];
|
|
6
|
+
readonly paths: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function loadAssetConfig(cwd: string): LoadAssetConfigResult {
|
|
10
|
+
let pkg: { forgeax?: { assets?: { roots?: unknown; paths?: unknown } } };
|
|
11
|
+
try {
|
|
12
|
+
pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
|
|
13
|
+
} catch {
|
|
14
|
+
return { roots: [join(cwd, 'assets')], paths: {} };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const assets = pkg.forgeax?.assets;
|
|
18
|
+
|
|
19
|
+
const rawRoots = assets?.roots;
|
|
20
|
+
const roots: readonly string[] =
|
|
21
|
+
Array.isArray(rawRoots) && rawRoots.length > 0
|
|
22
|
+
? (rawRoots as string[]).map((r) => join(cwd, r))
|
|
23
|
+
: [join(cwd, 'assets')];
|
|
24
|
+
|
|
25
|
+
const rawPaths = assets?.paths;
|
|
26
|
+
const paths: Record<string, string> = {};
|
|
27
|
+
if (rawPaths !== undefined && rawPaths !== null && typeof rawPaths === 'object') {
|
|
28
|
+
for (const [name, dir] of Object.entries(rawPaths as Record<string, unknown>)) {
|
|
29
|
+
if (typeof dir === 'string') {
|
|
30
|
+
paths[name] = join(cwd, dir);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { roots, paths };
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last path segment of a `/`- or `\`-separated path, trailing separators
|
|
3
|
+
* stripped. Inlined rather than imported from `node:path` so this module stays
|
|
4
|
+
* browser-safe: the dev-server pack path (`vite-plugin-pack` `/__pack/`) runs
|
|
5
|
+
* `buildCatalog` -> `deriveAssetName` in client code where `node:path` is
|
|
6
|
+
* externalized and `path.basename` throws.
|
|
7
|
+
*/
|
|
8
|
+
function baseName(p: string): string {
|
|
9
|
+
const trimmed = p.replace(/[/\\]+$/, '');
|
|
10
|
+
const idx = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'));
|
|
11
|
+
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Derive an asset's display name from package metadata.
|
|
16
|
+
*
|
|
17
|
+
* Pure function -- zero IO, zero side effects, browser-safe. Build-time
|
|
18
|
+
* (build-catalog) and runtime (resolveName) share this single source of truth
|
|
19
|
+
* for the XOR name-resolution rules (plan-strategy D-6).
|
|
20
|
+
*
|
|
21
|
+
* Branches:
|
|
22
|
+
* 1. single-asset package (assetCount === 1) -> baseName(packagePath)
|
|
23
|
+
* 2. multi-asset package + storedName -> storedName
|
|
24
|
+
* 3. multi-asset package + no storedName (AC-15.1) -> baseName(packagePath)
|
|
25
|
+
* 4. null packagePath + no storedName (AC-15.2) -> ''
|
|
26
|
+
*/
|
|
27
|
+
export function deriveAssetName(
|
|
28
|
+
packagePath: string | null,
|
|
29
|
+
assetCount: number,
|
|
30
|
+
storedName?: string,
|
|
31
|
+
): string {
|
|
32
|
+
if (packagePath === null) {
|
|
33
|
+
return storedName ?? '';
|
|
34
|
+
}
|
|
35
|
+
if (assetCount === 1) {
|
|
36
|
+
return baseName(packagePath);
|
|
37
|
+
}
|
|
38
|
+
if (storedName !== undefined) {
|
|
39
|
+
return storedName;
|
|
40
|
+
}
|
|
41
|
+
return baseName(packagePath);
|
|
42
|
+
}
|