@0xsequence/catapult 1.3.17 → 1.5.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 +276 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +12 -0
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/provenance.d.ts +3 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +138 -0
- package/dist/commands/provenance.js.map +1 -0
- package/dist/lib/__tests__/deployer.spec.js +118 -1
- package/dist/lib/__tests__/deployer.spec.js.map +1 -1
- package/dist/lib/__tests__/network-loader.spec.js.map +1 -1
- package/dist/lib/__tests__/provenance.spec.d.ts +2 -0
- package/dist/lib/__tests__/provenance.spec.d.ts.map +1 -0
- package/dist/lib/__tests__/provenance.spec.js +205 -0
- package/dist/lib/__tests__/provenance.spec.js.map +1 -0
- package/dist/lib/contracts/__tests__/repository.spec.js +243 -0
- package/dist/lib/contracts/__tests__/repository.spec.js.map +1 -1
- package/dist/lib/contracts/repository.d.ts +9 -1
- package/dist/lib/contracts/repository.d.ts.map +1 -1
- package/dist/lib/contracts/repository.js +93 -7
- package/dist/lib/contracts/repository.js.map +1 -1
- package/dist/lib/core/__tests__/assert-action.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/assert-action.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/assert-action.spec.js +377 -0
- package/dist/lib/core/__tests__/assert-action.spec.js.map +1 -0
- package/dist/lib/core/__tests__/engine.spec.js +80 -0
- package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
- package/dist/lib/core/__tests__/loader.spec.js +29 -0
- package/dist/lib/core/__tests__/loader.spec.js.map +1 -1
- package/dist/lib/core/__tests__/resolver.spec.js +405 -0
- package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
- package/dist/lib/core/__tests__/sign-actions.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/sign-actions.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/sign-actions.spec.js +128 -0
- package/dist/lib/core/__tests__/sign-actions.spec.js.map +1 -0
- package/dist/lib/core/__tests__/signer.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/signer.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/signer.spec.js +40 -0
- package/dist/lib/core/__tests__/signer.spec.js.map +1 -0
- package/dist/lib/core/context.d.ts +3 -2
- package/dist/lib/core/context.d.ts.map +1 -1
- package/dist/lib/core/context.js +3 -2
- package/dist/lib/core/context.js.map +1 -1
- package/dist/lib/core/engine.d.ts +4 -0
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js +206 -0
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/core/loader.d.ts +1 -0
- package/dist/lib/core/loader.d.ts.map +1 -1
- package/dist/lib/core/loader.js +6 -1
- package/dist/lib/core/loader.js.map +1 -1
- package/dist/lib/core/resolver.d.ts +2 -0
- package/dist/lib/core/resolver.d.ts.map +1 -1
- package/dist/lib/core/resolver.js +89 -0
- package/dist/lib/core/resolver.js.map +1 -1
- package/dist/lib/core/signer.d.ts +7 -0
- package/dist/lib/core/signer.d.ts.map +1 -0
- package/dist/lib/core/signer.js +60 -0
- package/dist/lib/core/signer.js.map +1 -0
- package/dist/lib/deployer.d.ts.map +1 -1
- package/dist/lib/deployer.js +21 -4
- package/dist/lib/deployer.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/parsers/__tests__/job.spec.js +77 -0
- package/dist/lib/parsers/__tests__/job.spec.js.map +1 -1
- package/dist/lib/parsers/__tests__/source.spec.d.ts +2 -0
- package/dist/lib/parsers/__tests__/source.spec.d.ts.map +1 -0
- package/dist/lib/parsers/__tests__/source.spec.js +158 -0
- package/dist/lib/parsers/__tests__/source.spec.js.map +1 -0
- package/dist/lib/parsers/index.d.ts +1 -0
- package/dist/lib/parsers/index.d.ts.map +1 -1
- package/dist/lib/parsers/index.js +1 -0
- package/dist/lib/parsers/index.js.map +1 -1
- package/dist/lib/parsers/job.d.ts.map +1 -1
- package/dist/lib/parsers/job.js +11 -0
- package/dist/lib/parsers/job.js.map +1 -1
- package/dist/lib/parsers/source.d.ts +4 -0
- package/dist/lib/parsers/source.d.ts.map +1 -0
- package/dist/lib/parsers/source.js +107 -0
- package/dist/lib/parsers/source.js.map +1 -0
- package/dist/lib/provenance.d.ts +34 -0
- package/dist/lib/provenance.d.ts.map +1 -0
- package/dist/lib/provenance.js +694 -0
- package/dist/lib/provenance.js.map +1 -0
- package/dist/lib/types/actions.d.ts +42 -2
- package/dist/lib/types/actions.d.ts.map +1 -1
- package/dist/lib/types/actions.js +4 -0
- package/dist/lib/types/actions.js.map +1 -1
- package/dist/lib/types/contracts.d.ts +3 -0
- package/dist/lib/types/contracts.d.ts.map +1 -1
- package/dist/lib/types/definitions.d.ts +1 -0
- package/dist/lib/types/definitions.d.ts.map +1 -1
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/source.d.ts +26 -0
- package/dist/lib/types/source.d.ts.map +1 -0
- package/dist/lib/types/source.js +3 -0
- package/dist/lib/types/source.js.map +1 -0
- package/dist/lib/types/values.d.ts +33 -1
- package/dist/lib/types/values.d.ts.map +1 -1
- package/package.json +4 -1
- package/.eslintrc.json +0 -29
- package/.github/workflows/ci.yml +0 -181
- package/CONCEPT.md +0 -24
- package/contracts/checked-call.huff +0 -65
- package/eslint.config.js +0 -48
- package/examples/jobs/guards-v1.yaml +0 -17
- package/examples/jobs/sequence-seq-0001-patch.yaml +0 -59
- package/examples/jobs/sequence-v1.yaml +0 -59
- package/examples/templates/sequence-factory-v1.yaml +0 -56
- package/jest.config.js +0 -25
- package/src/cli.ts +0 -17
- package/src/commands/common.ts +0 -61
- package/src/commands/dry.ts +0 -209
- package/src/commands/etherscan.ts +0 -360
- package/src/commands/index.ts +0 -5
- package/src/commands/list.ts +0 -249
- package/src/commands/run.ts +0 -146
- package/src/commands/utils.ts +0 -215
- package/src/index.ts +0 -67
- package/src/lib/__tests__/deployer-events.spec.ts +0 -338
- package/src/lib/__tests__/deployer.spec.ts +0 -2093
- package/src/lib/__tests__/network-loader.spec.ts +0 -150
- package/src/lib/__tests__/network-selection.spec.ts +0 -41
- package/src/lib/__tests__/network-utils.spec.ts +0 -230
- package/src/lib/artifacts/__tests__/fixtures/contract1.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/contract2.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +0 -18
- package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +0 -8
- package/src/lib/artifacts/__tests__/fixtures/readme.txt +0 -2
- package/src/lib/contracts/__tests__/repository.spec.ts +0 -344
- package/src/lib/contracts/repository.ts +0 -313
- package/src/lib/core/__tests__/context.spec.ts +0 -37
- package/src/lib/core/__tests__/engine.spec.ts +0 -1889
- package/src/lib/core/__tests__/graph.spec.ts +0 -125
- package/src/lib/core/__tests__/json-integration.spec.ts +0 -425
- package/src/lib/core/__tests__/loader.spec.ts +0 -334
- package/src/lib/core/__tests__/multi-platform-verification.spec.ts +0 -406
- package/src/lib/core/__tests__/resolver.spec.ts +0 -2053
- package/src/lib/core/__tests__/static-action.spec.ts +0 -172
- package/src/lib/core/context.ts +0 -127
- package/src/lib/core/engine.ts +0 -1782
- package/src/lib/core/graph.ts +0 -252
- package/src/lib/core/loader.ts +0 -247
- package/src/lib/core/resolver.ts +0 -757
- package/src/lib/deployer.ts +0 -981
- package/src/lib/events/__tests__/event-system.spec.ts +0 -392
- package/src/lib/events/cli-adapter.ts +0 -369
- package/src/lib/events/emitter.ts +0 -62
- package/src/lib/events/index.ts +0 -3
- package/src/lib/events/types.ts +0 -520
- package/src/lib/index.ts +0 -14
- package/src/lib/network-loader.ts +0 -90
- package/src/lib/network-selection.ts +0 -73
- package/src/lib/network-utils.ts +0 -64
- package/src/lib/parsers/__tests__/buildinfo.spec.ts +0 -122
- package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +0 -62
- package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +0 -2
- package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +0 -89
- package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +0 -17
- package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +0 -63
- package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +0 -4
- package/src/lib/parsers/__tests__/job.spec.ts +0 -358
- package/src/lib/parsers/__tests__/template.spec.ts +0 -111
- package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +0 -117
- package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +0 -67
- package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +0 -4
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +0 -40
- package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +0 -7
- package/src/lib/parsers/artifact/foundry-1.2.ts +0 -72
- package/src/lib/parsers/artifact/index.ts +0 -27
- package/src/lib/parsers/artifact/types.ts +0 -9
- package/src/lib/parsers/buildinfo.ts +0 -127
- package/src/lib/parsers/constants.ts +0 -56
- package/src/lib/parsers/index.ts +0 -5
- package/src/lib/parsers/job.ts +0 -148
- package/src/lib/parsers/template.ts +0 -135
- package/src/lib/std/templates/arachnid-deterministic-deployment-proxy.yaml +0 -68
- package/src/lib/std/templates/assured-deployment.yaml +0 -46
- package/src/lib/std/templates/era-evm-predeploy.yaml +0 -35
- package/src/lib/std/templates/erc-2470.yaml +0 -70
- package/src/lib/std/templates/min-balance.yaml +0 -35
- package/src/lib/std/templates/nano-universal-deployer.yaml +0 -61
- package/src/lib/std/templates/raw-erc-2470.yaml +0 -62
- package/src/lib/std/templates/raw-nano-universal-deployer.yaml +0 -54
- package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +0 -52
- package/src/lib/std/templates/sequence-universal-deployer-2.yaml +0 -61
- package/src/lib/types/__tests__/json-request-action.spec.ts +0 -243
- package/src/lib/types/__tests__/read-json-value.spec.ts +0 -278
- package/src/lib/types/__tests__/resolve-json-value.spec.ts +0 -769
- package/src/lib/types/actions.ts +0 -127
- package/src/lib/types/artifacts.ts +0 -21
- package/src/lib/types/buildinfo.ts +0 -116
- package/src/lib/types/conditions.ts +0 -50
- package/src/lib/types/contracts.ts +0 -23
- package/src/lib/types/definitions.ts +0 -70
- package/src/lib/types/index.ts +0 -8
- package/src/lib/types/network.ts +0 -33
- package/src/lib/types/project.ts +0 -9
- package/src/lib/types/task.ts +0 -9
- package/src/lib/types/values.ts +0 -150
- package/src/lib/utils/assertion.ts +0 -24
- package/src/lib/utils/validation.ts +0 -116
- package/src/lib/validation/contract-references.ts +0 -210
- package/src/lib/validation/index.ts +0 -1
- package/src/lib/verification/__tests__/etherscan.spec.ts +0 -710
- package/src/lib/verification/__tests__/sourcify.spec.ts +0 -288
- package/src/lib/verification/etherscan.ts +0 -547
- package/src/lib/verification/sourcify.ts +0 -248
- package/test_validation/artifacts/TestContract.json +0 -9
- package/test_validation/jobs/test-missing.yaml +0 -16
- package/test_validation/networks.yaml +0 -3
- package/tsconfig.json +0 -36
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.collectSourceProvenanceEntries = collectSourceProvenanceEntries;
|
|
37
|
+
exports.verifySourceProvenance = verifySourceProvenance;
|
|
38
|
+
exports.generateBuildInfoFromSourceProvenance = generateBuildInfoFromSourceProvenance;
|
|
39
|
+
const fs = __importStar(require("fs/promises"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const util_1 = require("util");
|
|
44
|
+
const graph_1 = require("./core/graph");
|
|
45
|
+
const loader_1 = require("./core/loader");
|
|
46
|
+
const source_1 = require("./parsers/source");
|
|
47
|
+
const buildinfo_1 = require("./parsers/buildinfo");
|
|
48
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
49
|
+
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.git', '.idea', '.vscode']);
|
|
50
|
+
const COMMAND_MAX_BUFFER = 20 * 1024 * 1024;
|
|
51
|
+
const BUILD_INFO_ID_PLACEHOLDER = '<build-info-id>';
|
|
52
|
+
const BUILD_INFO_BASE_PATH_PLACEHOLDER = '<build-info-base-path>';
|
|
53
|
+
const CATAPULT_CHECKOUT_PATH_PATTERN = /(?:[A-Za-z]:)?[/\\](?:[^/\\]+[/\\])*catapult-provenance-[^/\\]+[/\\]repo(?=[/\\]|$)/g;
|
|
54
|
+
async function collectSourceProvenanceEntries(projectRoot, options = {}) {
|
|
55
|
+
const absoluteProjectRoot = path.resolve(projectRoot);
|
|
56
|
+
const sourceFiles = await findSourceFiles(absoluteProjectRoot);
|
|
57
|
+
const warnings = [];
|
|
58
|
+
const entries = [];
|
|
59
|
+
for (const sourceFilePath of sourceFiles) {
|
|
60
|
+
let sourceDocument;
|
|
61
|
+
try {
|
|
62
|
+
const content = await fs.readFile(sourceFilePath, 'utf-8');
|
|
63
|
+
sourceDocument = (0, source_1.parseSourceDocument)(content);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
warnings.push(`Skipping source provenance file ${sourceFilePath}: ${formatError(error)}`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!sourceDocument) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
for (const warning of sourceDocument.warnings || []) {
|
|
73
|
+
warnings.push(`Skipping source provenance entry ${sourceFilePath}: ${warning}`);
|
|
74
|
+
}
|
|
75
|
+
const sourceDir = path.dirname(sourceFilePath);
|
|
76
|
+
for (const [buildInfoRef, provenance] of Object.entries(sourceDocument.build_info)) {
|
|
77
|
+
const buildInfoPath = path.resolve(sourceDir, buildInfoRef);
|
|
78
|
+
if (!(0, buildinfo_1.isBuildInfoFile)(buildInfoPath)) {
|
|
79
|
+
warnings.push(`Skipping source provenance entry ${sourceFilePath}: "${buildInfoRef}" does not point to a build-info JSON file.`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
entries.push({
|
|
83
|
+
sourceDocumentPath: sourceFilePath,
|
|
84
|
+
buildInfoRef,
|
|
85
|
+
buildInfoPath,
|
|
86
|
+
provenance
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (options.jobs && options.jobs.length > 0) {
|
|
91
|
+
return {
|
|
92
|
+
entries: await filterEntriesByJobs(absoluteProjectRoot, entries, options),
|
|
93
|
+
warnings
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { entries, warnings };
|
|
97
|
+
}
|
|
98
|
+
async function verifySourceProvenance(projectRoot, options = {}) {
|
|
99
|
+
const collected = await collectSourceProvenanceEntries(projectRoot, options);
|
|
100
|
+
const buildCache = new Map();
|
|
101
|
+
const results = [];
|
|
102
|
+
try {
|
|
103
|
+
for (const entry of collected.entries) {
|
|
104
|
+
results.push(await verifySourceProvenanceEntry(entry, buildCache));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await cleanupBuildCache(buildCache);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...collected,
|
|
112
|
+
results
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function generateBuildInfoFromSourceProvenance(projectRoot, options = {}) {
|
|
116
|
+
const collected = await collectSourceProvenanceEntries(projectRoot, options);
|
|
117
|
+
const buildCache = new Map();
|
|
118
|
+
const results = [];
|
|
119
|
+
try {
|
|
120
|
+
for (const entry of collected.entries) {
|
|
121
|
+
results.push(await generateBuildInfoForEntry(entry, buildCache, options.force === true));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await cleanupBuildCache(buildCache);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
...collected,
|
|
129
|
+
results
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function verifySourceProvenanceEntry(entry, buildCache) {
|
|
133
|
+
try {
|
|
134
|
+
await assertPathExists(entry.buildInfoPath);
|
|
135
|
+
const buildOutput = await getBuildOutput(entry, buildCache);
|
|
136
|
+
const generated = await selectGeneratedBuildInfo(entry, buildOutput.candidates);
|
|
137
|
+
const comparison = await compareJsonFiles(entry.buildInfoPath, generated);
|
|
138
|
+
if (!comparison.matches) {
|
|
139
|
+
return {
|
|
140
|
+
entry,
|
|
141
|
+
status: 'failed',
|
|
142
|
+
message: `Committed build-info does not match generated ${generated.relativePath}.${comparison.difference ? ` First difference: ${comparison.difference}.` : ''}`,
|
|
143
|
+
generatedBuildInfoPath: generated.filePath
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
entry,
|
|
148
|
+
status: 'verified',
|
|
149
|
+
message: `Matches generated ${generated.relativePath} at ${shortCommit(buildOutput.head)}.`,
|
|
150
|
+
generatedBuildInfoPath: generated.filePath
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
entry,
|
|
156
|
+
status: 'failed',
|
|
157
|
+
message: formatError(error)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function generateBuildInfoForEntry(entry, buildCache, force) {
|
|
162
|
+
try {
|
|
163
|
+
if (!force && await pathExists(entry.buildInfoPath)) {
|
|
164
|
+
return {
|
|
165
|
+
entry,
|
|
166
|
+
status: 'skipped',
|
|
167
|
+
message: 'Build-info already exists. Use --force to overwrite it.'
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const buildOutput = await getBuildOutput(entry, buildCache);
|
|
171
|
+
const generated = await selectGeneratedBuildInfo(entry, buildOutput.candidates);
|
|
172
|
+
await fs.mkdir(path.dirname(entry.buildInfoPath), { recursive: true });
|
|
173
|
+
await fs.writeFile(entry.buildInfoPath, generated.content);
|
|
174
|
+
return {
|
|
175
|
+
entry,
|
|
176
|
+
status: 'generated',
|
|
177
|
+
message: `Wrote build-info from generated ${generated.relativePath} at ${shortCommit(buildOutput.head)}.`,
|
|
178
|
+
generatedBuildInfoPath: generated.filePath
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return {
|
|
183
|
+
entry,
|
|
184
|
+
status: 'failed',
|
|
185
|
+
message: formatError(error)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function getBuildOutput(entry, buildCache) {
|
|
190
|
+
const key = buildCacheKey(entry.provenance);
|
|
191
|
+
if (!buildCache.has(key)) {
|
|
192
|
+
buildCache.set(key, buildFromSourceProvenance(entry.provenance));
|
|
193
|
+
}
|
|
194
|
+
return buildCache.get(key);
|
|
195
|
+
}
|
|
196
|
+
async function buildFromSourceProvenance(provenance) {
|
|
197
|
+
if (!provenance.build) {
|
|
198
|
+
throw new Error('source provenance is missing a build command.');
|
|
199
|
+
}
|
|
200
|
+
if (!provenance.commit && !provenance.ref) {
|
|
201
|
+
throw new Error('source provenance must include either commit or ref.');
|
|
202
|
+
}
|
|
203
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'catapult-provenance-'));
|
|
204
|
+
const checkoutDir = path.join(tempDir, 'repo');
|
|
205
|
+
try {
|
|
206
|
+
await runGit(['clone', '--', provenance.repo, checkoutDir]);
|
|
207
|
+
if (provenance.ref) {
|
|
208
|
+
const refHead = await gitCommitForRevision(provenance.ref, checkoutDir);
|
|
209
|
+
if (provenance.commit && !commitMatches(refHead, provenance.commit)) {
|
|
210
|
+
throw new Error(`ref "${provenance.ref}" resolves to ${refHead}, not ${provenance.commit}.`);
|
|
211
|
+
}
|
|
212
|
+
await checkoutGitCommit(refHead, checkoutDir);
|
|
213
|
+
}
|
|
214
|
+
else if (provenance.commit) {
|
|
215
|
+
const commitHead = await gitCommitForRevision(provenance.commit, checkoutDir);
|
|
216
|
+
await checkoutGitCommit(commitHead, checkoutDir);
|
|
217
|
+
}
|
|
218
|
+
const head = await gitStdout(['rev-parse', 'HEAD'], checkoutDir);
|
|
219
|
+
await runBuild(provenance.build, checkoutDir, provenance.image);
|
|
220
|
+
const candidates = await findBuildInfoCandidates(checkoutDir);
|
|
221
|
+
if (candidates.length === 0) {
|
|
222
|
+
throw new Error('build command did not produce any build-info JSON files.');
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
tempDir,
|
|
226
|
+
checkoutDir,
|
|
227
|
+
head,
|
|
228
|
+
candidates
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function cleanupBuildCache(buildCache) {
|
|
237
|
+
const seen = new Set();
|
|
238
|
+
for (const buildOutputPromise of buildCache.values()) {
|
|
239
|
+
try {
|
|
240
|
+
const buildOutput = await buildOutputPromise;
|
|
241
|
+
if (!seen.has(buildOutput.tempDir)) {
|
|
242
|
+
seen.add(buildOutput.tempDir);
|
|
243
|
+
await fs.rm(buildOutput.tempDir, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function findBuildInfoCandidates(root) {
|
|
251
|
+
const files = await findJsonFiles(root);
|
|
252
|
+
const candidates = [];
|
|
253
|
+
for (const filePath of files) {
|
|
254
|
+
if (!(0, buildinfo_1.isBuildInfoFile)(filePath)) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
259
|
+
const json = JSON.parse(content);
|
|
260
|
+
candidates.push({
|
|
261
|
+
filePath,
|
|
262
|
+
relativePath: path.relative(root, filePath),
|
|
263
|
+
content,
|
|
264
|
+
json,
|
|
265
|
+
id: getJsonId(json)
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return candidates;
|
|
272
|
+
}
|
|
273
|
+
async function selectGeneratedBuildInfo(entry, candidates) {
|
|
274
|
+
if (candidates.length === 0) {
|
|
275
|
+
throw new Error('build command did not produce any build-info JSON files.');
|
|
276
|
+
}
|
|
277
|
+
const existingJson = await readJsonIfExists(entry.buildInfoPath);
|
|
278
|
+
const existingId = getJsonId(existingJson);
|
|
279
|
+
const selectionIssues = [];
|
|
280
|
+
if (existingId) {
|
|
281
|
+
const idMatches = candidates.filter(candidate => candidate.id === existingId);
|
|
282
|
+
if (idMatches.length === 1) {
|
|
283
|
+
return idMatches[0];
|
|
284
|
+
}
|
|
285
|
+
if (idMatches.length > 1) {
|
|
286
|
+
selectionIssues.push(`${idMatches.length} candidates share id "${existingId}"`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const targetBaseName = path.basename(entry.buildInfoPath);
|
|
290
|
+
const nameMatches = candidates.filter(candidate => path.basename(candidate.filePath) === targetBaseName);
|
|
291
|
+
if (nameMatches.length === 1) {
|
|
292
|
+
return nameMatches[0];
|
|
293
|
+
}
|
|
294
|
+
if (nameMatches.length > 1) {
|
|
295
|
+
selectionIssues.push(`${nameMatches.length} candidates are named "${targetBaseName}"`);
|
|
296
|
+
}
|
|
297
|
+
if (candidates.length === 1) {
|
|
298
|
+
return candidates[0];
|
|
299
|
+
}
|
|
300
|
+
throw new Error(`Generated ${candidates.length} build-info files and could not select one for "${entry.buildInfoRef}". ` +
|
|
301
|
+
`${selectionIssues.length > 0 ? `${selectionIssues.join('; ')}. ` : ''}` +
|
|
302
|
+
`Use a build command that emits one file or names the file "${targetBaseName}".`);
|
|
303
|
+
}
|
|
304
|
+
async function compareJsonFiles(existingPath, generated) {
|
|
305
|
+
const existingRaw = await fs.readFile(existingPath, 'utf-8');
|
|
306
|
+
const existingJson = JSON.parse(existingRaw);
|
|
307
|
+
const normalizedExisting = sortJson(normalizeBuildInfoJson(existingJson));
|
|
308
|
+
const normalizedGenerated = sortJson(normalizeBuildInfoJson(generated.json));
|
|
309
|
+
if (JSON.stringify(normalizedExisting) === JSON.stringify(normalizedGenerated)) {
|
|
310
|
+
return { matches: true };
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
matches: false,
|
|
314
|
+
difference: findFirstJsonDifference(normalizedExisting, normalizedGenerated)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async function filterEntriesByJobs(projectRoot, entries, options) {
|
|
318
|
+
const loader = new loader_1.ProjectLoader(projectRoot, {
|
|
319
|
+
loadStdTemplates: options.loadStdTemplates,
|
|
320
|
+
loadContracts: false
|
|
321
|
+
});
|
|
322
|
+
await loader.load();
|
|
323
|
+
const graph = new graph_1.DependencyGraph(loader.jobs, loader.templates);
|
|
324
|
+
const selectedJobs = selectJobNames(loader.jobs, graph, options.jobs || [], options.includeDependencies === true);
|
|
325
|
+
const scopeRoots = selectedJobs.flatMap(jobName => {
|
|
326
|
+
const job = loader.jobs.get(jobName);
|
|
327
|
+
return job ? jobScopeRoots(projectRoot, job) : [];
|
|
328
|
+
});
|
|
329
|
+
return entries.filter(entry => scopeRoots.some(root => isPathWithin(root, entry.sourceDocumentPath) || isPathWithin(root, entry.buildInfoPath)));
|
|
330
|
+
}
|
|
331
|
+
function selectJobNames(jobs, graph, patterns, includeDependencies) {
|
|
332
|
+
const allJobNames = Array.from(jobs.keys());
|
|
333
|
+
const requested = new Set();
|
|
334
|
+
for (const pattern of patterns) {
|
|
335
|
+
const matches = isPattern(pattern)
|
|
336
|
+
? allJobNames.filter(jobName => patternToRegex(pattern).test(jobName))
|
|
337
|
+
: allJobNames.filter(jobName => jobName === pattern);
|
|
338
|
+
if (matches.length === 0) {
|
|
339
|
+
throw new Error(`Job "${pattern}" not found in project.`);
|
|
340
|
+
}
|
|
341
|
+
matches.forEach(match => requested.add(match));
|
|
342
|
+
}
|
|
343
|
+
const selected = new Set(requested);
|
|
344
|
+
if (includeDependencies) {
|
|
345
|
+
for (const jobName of requested) {
|
|
346
|
+
graph.getDependencies(jobName).forEach(dep => selected.add(dep));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return graph.getExecutionOrder().filter(jobName => selected.has(jobName));
|
|
350
|
+
}
|
|
351
|
+
function jobScopeRoots(projectRoot, job) {
|
|
352
|
+
if (!job._path) {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
const jobsRoot = path.resolve(projectRoot, 'jobs');
|
|
356
|
+
const jobPath = path.resolve(job._path);
|
|
357
|
+
const jobDir = path.dirname(jobPath);
|
|
358
|
+
const jobBaseDir = path.join(jobDir, path.basename(jobPath, path.extname(jobPath)));
|
|
359
|
+
if (path.normalize(jobDir) === path.normalize(jobsRoot)) {
|
|
360
|
+
return [jobBaseDir];
|
|
361
|
+
}
|
|
362
|
+
return Array.from(new Set([jobDir, jobBaseDir]));
|
|
363
|
+
}
|
|
364
|
+
async function findSourceFiles(root) {
|
|
365
|
+
const results = [];
|
|
366
|
+
await walk(root, async (filePath, direntName) => {
|
|
367
|
+
if (direntName === 'source.yaml' || direntName === 'source.yml') {
|
|
368
|
+
results.push(filePath);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return results.sort();
|
|
372
|
+
}
|
|
373
|
+
async function findJsonFiles(root) {
|
|
374
|
+
const results = [];
|
|
375
|
+
await walk(root, async (filePath, direntName) => {
|
|
376
|
+
if (direntName.endsWith('.json')) {
|
|
377
|
+
results.push(filePath);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return results.sort();
|
|
381
|
+
}
|
|
382
|
+
async function walk(root, onFile) {
|
|
383
|
+
let dirents;
|
|
384
|
+
try {
|
|
385
|
+
dirents = await fs.readdir(root, { withFileTypes: true });
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
for (const dirent of dirents) {
|
|
391
|
+
const fullPath = path.resolve(root, dirent.name);
|
|
392
|
+
if (dirent.isDirectory()) {
|
|
393
|
+
if (!IGNORED_DIRS.has(dirent.name)) {
|
|
394
|
+
await walk(fullPath, onFile);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (dirent.isFile()) {
|
|
398
|
+
await onFile(fullPath, dirent.name);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async function runGit(args, cwd) {
|
|
403
|
+
try {
|
|
404
|
+
await execFileAsync('git', args, { cwd, maxBuffer: COMMAND_MAX_BUFFER });
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
throw new Error(`git ${args.join(' ')} failed: ${commandErrorMessage(error)}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async function gitStdout(args, cwd) {
|
|
411
|
+
try {
|
|
412
|
+
const result = await execFileAsync('git', args, { cwd, maxBuffer: COMMAND_MAX_BUFFER });
|
|
413
|
+
return String(result.stdout).trim();
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
throw new Error(`git ${args.join(' ')} failed: ${commandErrorMessage(error)}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function gitCommitForRevision(revision, cwd) {
|
|
420
|
+
return gitStdout(['rev-parse', '--verify', '--end-of-options', `${revision}^{commit}`], cwd);
|
|
421
|
+
}
|
|
422
|
+
async function checkoutGitCommit(commit, cwd) {
|
|
423
|
+
await runGit(['checkout', '--detach', commit], cwd);
|
|
424
|
+
}
|
|
425
|
+
async function runBuild(command, cwd, image) {
|
|
426
|
+
if (image) {
|
|
427
|
+
await runDockerBuild(command, cwd, image);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
await runShell(command, cwd);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const CONTAINER_WORKDIR = '/workspace';
|
|
434
|
+
async function runDockerBuild(command, cwd, image) {
|
|
435
|
+
const args = [
|
|
436
|
+
'run',
|
|
437
|
+
'--rm',
|
|
438
|
+
'-v',
|
|
439
|
+
`${cwd}:${CONTAINER_WORKDIR}`,
|
|
440
|
+
'-w',
|
|
441
|
+
CONTAINER_WORKDIR,
|
|
442
|
+
'-e',
|
|
443
|
+
`HOME=${CONTAINER_WORKDIR}`
|
|
444
|
+
];
|
|
445
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
|
|
446
|
+
const gid = typeof process.getgid === 'function' ? process.getgid() : undefined;
|
|
447
|
+
if (uid !== undefined && gid !== undefined) {
|
|
448
|
+
args.push('--user', `${uid}:${gid}`);
|
|
449
|
+
}
|
|
450
|
+
args.push('--entrypoint', 'sh', image, '-c', command);
|
|
451
|
+
await new Promise((resolve, reject) => {
|
|
452
|
+
const child = (0, child_process_1.spawn)('docker', args, {
|
|
453
|
+
cwd,
|
|
454
|
+
env: process.env,
|
|
455
|
+
stdio: 'inherit'
|
|
456
|
+
});
|
|
457
|
+
child.on('error', error => {
|
|
458
|
+
reject(new Error(`failed to start docker for image "${image}" (is Docker installed and running?): ${error.message}`));
|
|
459
|
+
});
|
|
460
|
+
child.on('exit', (code, signal) => {
|
|
461
|
+
if (code === 0) {
|
|
462
|
+
resolve();
|
|
463
|
+
}
|
|
464
|
+
else if (signal) {
|
|
465
|
+
reject(new Error(`build command failed: terminated by ${signal}`));
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
reject(new Error(`build command failed: exited with code ${code}`));
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
async function runShell(command, cwd) {
|
|
474
|
+
await new Promise((resolve, reject) => {
|
|
475
|
+
const child = (0, child_process_1.spawn)(command, {
|
|
476
|
+
cwd,
|
|
477
|
+
env: process.env,
|
|
478
|
+
shell: true,
|
|
479
|
+
stdio: 'inherit'
|
|
480
|
+
});
|
|
481
|
+
child.on('error', error => {
|
|
482
|
+
reject(new Error(`build command failed to start: ${error.message}`));
|
|
483
|
+
});
|
|
484
|
+
child.on('exit', (code, signal) => {
|
|
485
|
+
if (code === 0) {
|
|
486
|
+
resolve();
|
|
487
|
+
}
|
|
488
|
+
else if (signal) {
|
|
489
|
+
reject(new Error(`build command failed: terminated by ${signal}`));
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
reject(new Error(`build command failed: exited with code ${code}`));
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async function assertPathExists(filePath) {
|
|
498
|
+
if (!await pathExists(filePath)) {
|
|
499
|
+
throw new Error(`build-info file does not exist: ${filePath}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function pathExists(filePath) {
|
|
503
|
+
try {
|
|
504
|
+
await fs.access(filePath);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async function readJsonIfExists(filePath) {
|
|
512
|
+
try {
|
|
513
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
514
|
+
return JSON.parse(content);
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function getJsonId(value) {
|
|
521
|
+
if (value && typeof value === 'object' && 'id' in value) {
|
|
522
|
+
const id = value.id;
|
|
523
|
+
return typeof id === 'string' ? id : undefined;
|
|
524
|
+
}
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
function buildCacheKey(provenance) {
|
|
528
|
+
return JSON.stringify({
|
|
529
|
+
repo: provenance.repo,
|
|
530
|
+
ref: provenance.ref,
|
|
531
|
+
commit: provenance.commit,
|
|
532
|
+
build: provenance.build,
|
|
533
|
+
image: provenance.image
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
function normalizeBuildInfoJson(value) {
|
|
537
|
+
return normalizeBuildInfoValue(value, [], getBuildInfoBasePath(value));
|
|
538
|
+
}
|
|
539
|
+
function normalizeBuildInfoValue(value, pathSegments, basePath) {
|
|
540
|
+
if (pathSegments.length === 1 && pathSegments[0] === 'id') {
|
|
541
|
+
return BUILD_INFO_ID_PLACEHOLDER;
|
|
542
|
+
}
|
|
543
|
+
if (typeof value === 'string') {
|
|
544
|
+
return normalizeBuildInfoString(value, basePath);
|
|
545
|
+
}
|
|
546
|
+
if (Array.isArray(value)) {
|
|
547
|
+
return value.map((item, index) => normalizeBuildInfoValue(item, [...pathSegments, String(index)], basePath));
|
|
548
|
+
}
|
|
549
|
+
if (value && typeof value === 'object') {
|
|
550
|
+
const objectValue = value;
|
|
551
|
+
return Object.fromEntries(Object.entries(objectValue).map(([key, item]) => [
|
|
552
|
+
normalizeBuildInfoString(key, basePath),
|
|
553
|
+
normalizeBuildInfoValue(item, [...pathSegments, key], basePath)
|
|
554
|
+
]));
|
|
555
|
+
}
|
|
556
|
+
return value;
|
|
557
|
+
}
|
|
558
|
+
function normalizeBuildInfoString(value, basePath) {
|
|
559
|
+
let normalized = value;
|
|
560
|
+
const basePathPattern = basePath && isAbsolutePath(basePath) ? pathPrefixPattern(basePath) : undefined;
|
|
561
|
+
if (basePathPattern) {
|
|
562
|
+
normalized = normalized.replace(basePathPattern, BUILD_INFO_BASE_PATH_PLACEHOLDER);
|
|
563
|
+
}
|
|
564
|
+
return normalized.replace(CATAPULT_CHECKOUT_PATH_PATTERN, BUILD_INFO_BASE_PATH_PLACEHOLDER);
|
|
565
|
+
}
|
|
566
|
+
function getBuildInfoBasePath(value) {
|
|
567
|
+
if (!value || typeof value !== 'object') {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
const input = value.input;
|
|
571
|
+
if (!input || typeof input !== 'object') {
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
const basePath = input.basePath;
|
|
575
|
+
return typeof basePath === 'string' && basePath.length > 0 ? basePath : undefined;
|
|
576
|
+
}
|
|
577
|
+
function pathPrefixPattern(value) {
|
|
578
|
+
const hasLeadingSeparator = /^[\\/]/.test(value);
|
|
579
|
+
const trimmed = value.replace(/^[\\/]+/, '').replace(/[\\/]+$/, '');
|
|
580
|
+
const segments = trimmed.split(/[\\/]+/).filter(Boolean);
|
|
581
|
+
if (segments.length === 0) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
const source = `${hasLeadingSeparator ? '[/\\\\]+' : ''}${segments.map(escapeRegex).join('[/\\\\]+')}`;
|
|
585
|
+
return new RegExp(`${source}(?=[/\\\\]|$)`, 'g');
|
|
586
|
+
}
|
|
587
|
+
function escapeRegex(value) {
|
|
588
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
589
|
+
}
|
|
590
|
+
function isAbsolutePath(value) {
|
|
591
|
+
return path.posix.isAbsolute(value) || path.win32.isAbsolute(value);
|
|
592
|
+
}
|
|
593
|
+
function findFirstJsonDifference(existing, generated, location = '$') {
|
|
594
|
+
if (Object.is(existing, generated)) {
|
|
595
|
+
return '';
|
|
596
|
+
}
|
|
597
|
+
if (Array.isArray(existing) || Array.isArray(generated)) {
|
|
598
|
+
if (!Array.isArray(existing) || !Array.isArray(generated)) {
|
|
599
|
+
return `${location} type differs: committed ${jsonType(existing)}, generated ${jsonType(generated)}`;
|
|
600
|
+
}
|
|
601
|
+
if (existing.length !== generated.length) {
|
|
602
|
+
return `${location} length differs: committed ${existing.length}, generated ${generated.length}`;
|
|
603
|
+
}
|
|
604
|
+
for (let i = 0; i < existing.length; i++) {
|
|
605
|
+
const difference = findFirstJsonDifference(existing[i], generated[i], `${location}[${i}]`);
|
|
606
|
+
if (difference) {
|
|
607
|
+
return difference;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return '';
|
|
611
|
+
}
|
|
612
|
+
if (isRecord(existing) || isRecord(generated)) {
|
|
613
|
+
if (!isRecord(existing) || !isRecord(generated)) {
|
|
614
|
+
return `${location} type differs: committed ${jsonType(existing)}, generated ${jsonType(generated)}`;
|
|
615
|
+
}
|
|
616
|
+
const keys = Array.from(new Set([...Object.keys(existing), ...Object.keys(generated)])).sort();
|
|
617
|
+
for (const key of keys) {
|
|
618
|
+
const nextLocation = jsonPath(location, key);
|
|
619
|
+
if (!(key in existing)) {
|
|
620
|
+
return `${nextLocation} is missing from committed build-info`;
|
|
621
|
+
}
|
|
622
|
+
if (!(key in generated)) {
|
|
623
|
+
return `${nextLocation} is missing from generated build-info`;
|
|
624
|
+
}
|
|
625
|
+
const difference = findFirstJsonDifference(existing[key], generated[key], nextLocation);
|
|
626
|
+
if (difference) {
|
|
627
|
+
return difference;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return '';
|
|
631
|
+
}
|
|
632
|
+
return `${location} differs: committed ${formatJsonScalar(existing)}, generated ${formatJsonScalar(generated)}`;
|
|
633
|
+
}
|
|
634
|
+
function isRecord(value) {
|
|
635
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
636
|
+
}
|
|
637
|
+
function jsonPath(location, key) {
|
|
638
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? `${location}.${key}` : `${location}[${JSON.stringify(key)}]`;
|
|
639
|
+
}
|
|
640
|
+
function jsonType(value) {
|
|
641
|
+
if (Array.isArray(value)) {
|
|
642
|
+
return 'array';
|
|
643
|
+
}
|
|
644
|
+
if (value === null) {
|
|
645
|
+
return 'null';
|
|
646
|
+
}
|
|
647
|
+
return typeof value;
|
|
648
|
+
}
|
|
649
|
+
function formatJsonScalar(value) {
|
|
650
|
+
const formatted = JSON.stringify(value);
|
|
651
|
+
return formatted && formatted.length > 120 ? `${formatted.slice(0, 117)}...` : formatted ?? String(value);
|
|
652
|
+
}
|
|
653
|
+
function sortJson(value) {
|
|
654
|
+
if (Array.isArray(value)) {
|
|
655
|
+
return value.map(sortJson);
|
|
656
|
+
}
|
|
657
|
+
if (value && typeof value === 'object') {
|
|
658
|
+
const objectValue = value;
|
|
659
|
+
return Object.fromEntries(Object.keys(objectValue).sort().map(key => [key, sortJson(objectValue[key])]));
|
|
660
|
+
}
|
|
661
|
+
return value;
|
|
662
|
+
}
|
|
663
|
+
function isPathWithin(root, candidate) {
|
|
664
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
665
|
+
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
666
|
+
}
|
|
667
|
+
function isPattern(value) {
|
|
668
|
+
return /[*?]/.test(value);
|
|
669
|
+
}
|
|
670
|
+
function patternToRegex(pattern) {
|
|
671
|
+
const escaped = pattern.replace(/[-\\^$+?.()|[\]{}]/g, '\\$&')
|
|
672
|
+
.replace(/\*/g, '.*')
|
|
673
|
+
.replace(/\?/g, '.');
|
|
674
|
+
return new RegExp(`^${escaped}$`);
|
|
675
|
+
}
|
|
676
|
+
function commitMatches(actual, expected) {
|
|
677
|
+
return actual === expected || actual.startsWith(expected);
|
|
678
|
+
}
|
|
679
|
+
function shortCommit(commit) {
|
|
680
|
+
return commit.slice(0, 12);
|
|
681
|
+
}
|
|
682
|
+
function commandErrorMessage(error) {
|
|
683
|
+
if (error && typeof error === 'object') {
|
|
684
|
+
const maybe = error;
|
|
685
|
+
const stderr = maybe.stderr ? String(maybe.stderr).trim() : '';
|
|
686
|
+
const stdout = maybe.stdout ? String(maybe.stdout).trim() : '';
|
|
687
|
+
return stderr || stdout || maybe.message || String(error);
|
|
688
|
+
}
|
|
689
|
+
return String(error);
|
|
690
|
+
}
|
|
691
|
+
function formatError(error) {
|
|
692
|
+
return error instanceof Error ? error.message : String(error);
|
|
693
|
+
}
|
|
694
|
+
//# sourceMappingURL=provenance.js.map
|