@kungfu-tech/buildchain 4.0.2-alpha.7 → 4.0.2-alpha.8
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/architecture/agent-change-map.md +5 -1
- package/architecture/internal-capabilities.json +3 -1
- package/architecture/maintainability-debt.json +11 -9
- package/architecture/maintainability-policy.json +6 -6
- package/architecture/v4-release-topology.json +73 -0
- package/dist/site/buildchain-contract.json +7 -7
- package/dist/site/buildchain-site.json +7 -7
- package/dist/site/kfd-claims.json +4 -3
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +3 -3
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +4 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +6 -5
- package/docs/node-api-reference.md +3 -3
- package/package.json +1 -1
- package/packages/core/release-tail-product-capabilities.js +29 -0
- package/packages/core/release-tail-provider-plane.js +2 -2
- package/packages/core/v4-canonical-contracts.js +4 -0
- package/packages/core/v4-product-publication.js +387 -0
- package/scripts/check-v4-release-topology.mjs +128 -21
- package/scripts/release-candidate-resolver.mjs +8 -9
- package/scripts/v4-product-publication-intent.mjs +114 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
|
+
import crypto from "node:crypto";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
parseWorkflowDocument,
|
|
@@ -17,6 +18,71 @@ function read(relative) {
|
|
|
17
18
|
return fs.readFileSync(path.join(root, relative), "utf8");
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
const LOCAL_MODULE_EXTENSIONS = ["", ".js", ".mjs", ".cjs"];
|
|
22
|
+
const PRIVILEGED_ENTRYPOINTS = [
|
|
23
|
+
"actions/v4-release-candidate-promote/index.js",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function localModuleSpecifiers(source) {
|
|
27
|
+
const specifiers = [];
|
|
28
|
+
const expression =
|
|
29
|
+
/(?:\bfrom\s+|\bimport\s*\(\s*|\bimport\s+)["'](\.[^"']+)["']/gu;
|
|
30
|
+
for (const match of source.matchAll(expression)) specifiers.push(match[1]);
|
|
31
|
+
return [...new Set(specifiers)].sort();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveLocalModule(importer, specifier) {
|
|
35
|
+
const unresolved = path.posix.normalize(
|
|
36
|
+
path.posix.join(path.posix.dirname(importer), specifier),
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
!unresolved.startsWith("../") && !path.posix.isAbsolute(unresolved),
|
|
40
|
+
`local module import escapes the repository: ${importer} -> ${specifier}`,
|
|
41
|
+
);
|
|
42
|
+
const candidates = [
|
|
43
|
+
...LOCAL_MODULE_EXTENSIONS.map((extension) => `${unresolved}${extension}`),
|
|
44
|
+
...LOCAL_MODULE_EXTENSIONS.slice(1).map((extension) =>
|
|
45
|
+
path.posix.join(unresolved, `index${extension}`),
|
|
46
|
+
),
|
|
47
|
+
];
|
|
48
|
+
const matches = candidates.filter((relative) => {
|
|
49
|
+
const absolute = path.join(root, relative);
|
|
50
|
+
return fs.existsSync(absolute) && fs.statSync(absolute).isFile();
|
|
51
|
+
});
|
|
52
|
+
assert.equal(
|
|
53
|
+
matches.length,
|
|
54
|
+
1,
|
|
55
|
+
`local module import must resolve exactly once: ${importer} -> ${specifier}`,
|
|
56
|
+
);
|
|
57
|
+
return matches[0];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function discoverStaticModuleClosure(entrypoints) {
|
|
61
|
+
const pending = [...entrypoints];
|
|
62
|
+
const visited = new Set();
|
|
63
|
+
while (pending.length > 0) {
|
|
64
|
+
const relative = pending.pop();
|
|
65
|
+
if (visited.has(relative)) continue;
|
|
66
|
+
assert.ok(
|
|
67
|
+
fs.statSync(path.join(root, relative)).isFile(),
|
|
68
|
+
`privileged entry is not a file: ${relative}`,
|
|
69
|
+
);
|
|
70
|
+
visited.add(relative);
|
|
71
|
+
for (const specifier of localModuleSpecifiers(read(relative))) {
|
|
72
|
+
const dependency = resolveLocalModule(relative, specifier);
|
|
73
|
+
if (!visited.has(dependency)) pending.push(dependency);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [...visited].sort();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function rootedPathSet(paths) {
|
|
80
|
+
return `sha256:${crypto
|
|
81
|
+
.createHash("sha256")
|
|
82
|
+
.update(JSON.stringify([...paths].sort()))
|
|
83
|
+
.digest("hex")}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
20
86
|
function productionFiles(relative, extensions) {
|
|
21
87
|
const absolute = path.join(root, relative);
|
|
22
88
|
if (!fs.existsSync(absolute)) return [];
|
|
@@ -61,6 +127,17 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
61
127
|
[".yml", ".yaml", ".js", ".mjs", ".cjs"],
|
|
62
128
|
/(?:createV4ReleaseReceipt|release-receipt\.json|V4_RELEASE_RECEIPT_CONTRACT)/u,
|
|
63
129
|
);
|
|
130
|
+
const privilegedModules = discoverStaticModuleClosure(PRIVILEGED_ENTRYPOINTS);
|
|
131
|
+
const legacyEngineModules = productionFiles(
|
|
132
|
+
"actions/promote-buildchain-ref",
|
|
133
|
+
[".js", ".mjs", ".cjs"],
|
|
134
|
+
)
|
|
135
|
+
.filter((relative) =>
|
|
136
|
+
/(?:^|\/)(?:lib|promote-(?:alpha|release|major)-channel|durable-transaction-operations)\.js$/u.test(
|
|
137
|
+
relative,
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
.sort();
|
|
64
141
|
return {
|
|
65
142
|
providerAdapters: [
|
|
66
143
|
...new Set([
|
|
@@ -71,6 +148,12 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
71
148
|
runtimeSelectors,
|
|
72
149
|
runtimeEngines: ["actions/v4-release-candidate-promote/index.js"],
|
|
73
150
|
terminalProjections,
|
|
151
|
+
privilegedExecutableClosure: {
|
|
152
|
+
entrypoints: PRIVILEGED_ENTRYPOINTS,
|
|
153
|
+
modules: privilegedModules,
|
|
154
|
+
root: rootedPathSet(privilegedModules),
|
|
155
|
+
},
|
|
156
|
+
legacyEngineModules,
|
|
74
157
|
};
|
|
75
158
|
}
|
|
76
159
|
|
|
@@ -274,6 +357,7 @@ function assertAuthorityClosure(ledger) {
|
|
|
274
357
|
"runtimeSelectors",
|
|
275
358
|
"runtimeEngines",
|
|
276
359
|
"terminalProjections",
|
|
360
|
+
"legacyEngineModules",
|
|
277
361
|
]) {
|
|
278
362
|
assert.ok(
|
|
279
363
|
Array.isArray(closure[className]) && closure[className].length > 0,
|
|
@@ -292,6 +376,24 @@ function assertAuthorityClosure(ledger) {
|
|
|
292
376
|
discovered[className],
|
|
293
377
|
`authority closure class drifted: ${className}`,
|
|
294
378
|
);
|
|
379
|
+
assert.deepEqual(
|
|
380
|
+
closure.privilegedExecutableClosure,
|
|
381
|
+
discovered.privilegedExecutableClosure,
|
|
382
|
+
"privileged executable transitive closure drifted",
|
|
383
|
+
);
|
|
384
|
+
assert.match(
|
|
385
|
+
closure.privilegedExecutableClosure.root,
|
|
386
|
+
/^sha256:[0-9a-f]{64}$/u,
|
|
387
|
+
);
|
|
388
|
+
const reachableLegacyEngines = closure.legacyEngineModules.filter(
|
|
389
|
+
(relative) =>
|
|
390
|
+
closure.privilegedExecutableClosure.modules.includes(relative),
|
|
391
|
+
);
|
|
392
|
+
assert.deepEqual(
|
|
393
|
+
reachableLegacyEngines,
|
|
394
|
+
[],
|
|
395
|
+
`legacy release engines remain reachable from APPLY: ${reachableLegacyEngines.join(", ")}`,
|
|
396
|
+
);
|
|
295
397
|
assert.deepEqual(closure.runtimeEngines, [
|
|
296
398
|
"actions/v4-release-candidate-promote/index.js",
|
|
297
399
|
]);
|
|
@@ -305,7 +407,7 @@ function assertAuthorityClosure(ledger) {
|
|
|
305
407
|
);
|
|
306
408
|
const engineSurface = [
|
|
307
409
|
".github/workflows/.release-candidate-promote.yml",
|
|
308
|
-
...closure.
|
|
410
|
+
...closure.privilegedExecutableClosure.modules,
|
|
309
411
|
]
|
|
310
412
|
.map(read)
|
|
311
413
|
.join("\n");
|
|
@@ -361,23 +463,28 @@ export function checkV4ReleaseTopology() {
|
|
|
361
463
|
return actual;
|
|
362
464
|
}
|
|
363
465
|
|
|
364
|
-
if (
|
|
365
|
-
process.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
466
|
+
if (
|
|
467
|
+
process.argv[1] &&
|
|
468
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
469
|
+
) {
|
|
470
|
+
if (process.argv.includes("--print-closure")) {
|
|
471
|
+
process.stdout.write(
|
|
472
|
+
`${JSON.stringify(discoverV4ReleaseAuthorityClosure(), null, 2)}\n`,
|
|
473
|
+
);
|
|
474
|
+
} else if (process.argv.includes("--print")) {
|
|
475
|
+
const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
|
|
476
|
+
process.stdout.write(
|
|
477
|
+
`${JSON.stringify(
|
|
478
|
+
discoverV4ReleaseTopology(
|
|
479
|
+
ledger.closedWorld.workflowPaths,
|
|
480
|
+
ledger.semanticScope.workflowPaths,
|
|
481
|
+
),
|
|
482
|
+
null,
|
|
483
|
+
2,
|
|
484
|
+
)}\n`,
|
|
485
|
+
);
|
|
486
|
+
} else {
|
|
487
|
+
checkV4ReleaseTopology();
|
|
488
|
+
process.stdout.write("v4 release topology: ok\n");
|
|
489
|
+
}
|
|
383
490
|
}
|
|
@@ -13,7 +13,6 @@ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
|
13
13
|
import { v4PublicationQualificationRoot, validateV4PublicationQualificationReceipt } from "../packages/core/v4-publication-qualification.js";
|
|
14
14
|
|
|
15
15
|
const DEFAULT_WORKFLOW_FILE = "build-surface-fixture.yml";
|
|
16
|
-
|
|
17
16
|
function env(name, fallback = "") {
|
|
18
17
|
return process.env[name] || fallback;
|
|
19
18
|
}
|
|
@@ -21,7 +20,6 @@ function env(name, fallback = "") {
|
|
|
21
20
|
export function releaseCandidateDownloadEnabled(value = "true") {
|
|
22
21
|
return String(value || "true").trim().toLowerCase() !== "false";
|
|
23
22
|
}
|
|
24
|
-
|
|
25
23
|
function splitRepository(repository) {
|
|
26
24
|
const match = String(repository || "").trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
27
25
|
if (!match) {
|
|
@@ -44,7 +42,9 @@ function assertSha(value, label = "sha") {
|
|
|
44
42
|
|
|
45
43
|
export const releaseCandidateRuntimeSha = (passport) =>
|
|
46
44
|
assertSha(passport?.buildchain?.sha, "release candidate Passport Buildchain runtime SHA").toLowerCase();
|
|
47
|
-
|
|
45
|
+
export const resolveFreshPublicationVersion = ({ sealedBundle, candidateVersion = "" } = {}) =>
|
|
46
|
+
String(sealedBundle?.manifest?.npm?.version || candidateVersion || "").trim();
|
|
47
|
+
const optionalText = (value) => String(value || "");
|
|
48
48
|
function githubHeaders(token) {
|
|
49
49
|
const headers = {
|
|
50
50
|
accept: "application/vnd.github+json",
|
|
@@ -740,19 +740,17 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
740
740
|
releaseAssetPaths,
|
|
741
741
|
})
|
|
742
742
|
: undefined;
|
|
743
|
-
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
743
|
+
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8"))), publicationVersion = resolveFreshPublicationVersion({ sealedBundle, candidateVersion: passport.target?.version });
|
|
744
744
|
const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
|
|
745
745
|
manifests,
|
|
746
|
-
version:
|
|
746
|
+
version: publicationVersion,
|
|
747
747
|
kind: publishArtifactKind,
|
|
748
748
|
tarballPaths: npmTarballPaths,
|
|
749
749
|
mainPackage: publishPackageMain,
|
|
750
750
|
});
|
|
751
751
|
const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
|
|
752
752
|
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(generatedRequiredArtifacts, null, 2)}\n`);
|
|
753
|
-
const sealedBundleManifestPath = sealedBundle
|
|
754
|
-
? path.join(resolvedOutput, "sealed-bundle.json")
|
|
755
|
-
: "";
|
|
753
|
+
const sealedBundleManifestPath = sealedBundle ? path.join(resolvedOutput, "sealed-bundle.json") : "";
|
|
756
754
|
if (sealedBundleManifestPath) {
|
|
757
755
|
fs.writeFileSync(sealedBundleManifestPath, `${JSON.stringify(sealedBundle.manifest, null, 2)}\n`);
|
|
758
756
|
}
|
|
@@ -772,6 +770,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
772
770
|
sealedBundleManifest: sealedBundleManifestPath ? outputPath(sealedBundleManifestPath) : "",
|
|
773
771
|
},
|
|
774
772
|
version: passport.target?.version || "",
|
|
773
|
+
publicationVersion,
|
|
775
774
|
candidateHash: passport.candidateHash || "",
|
|
776
775
|
payloadCount: payloadArtifacts.length,
|
|
777
776
|
platformManifestCount: platformManifestPaths.length,
|
|
@@ -811,6 +810,7 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
811
810
|
"release-candidate-publication-qualification-path": result.paths?.publicationQualification || "",
|
|
812
811
|
"release-candidate-publication-qualification-root": result.publicationQualificationRoot || "",
|
|
813
812
|
"release-candidate-version": result.version || "",
|
|
813
|
+
"release-candidate-publication-version": optionalText(result.publicationVersion),
|
|
814
814
|
"release-candidate-source-sha": result.artifacts?.sourceSha || "",
|
|
815
815
|
"release-candidate-artifact": result.artifacts?.passport || "",
|
|
816
816
|
"release-candidate-build-summary-artifact": result.artifacts?.summary || "",
|
|
@@ -843,7 +843,6 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
843
843
|
console.log(JSON.stringify(result, null, 2));
|
|
844
844
|
return result;
|
|
845
845
|
}
|
|
846
|
-
|
|
847
846
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
848
847
|
try {
|
|
849
848
|
await resolveReleaseCandidateArtifactsCli();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { spawnSyncCommand } from "../packages/core/spawn-command.js";
|
|
8
|
+
import { selectV4ProductPublicationIntent } from "../packages/core/v4-product-publication.js";
|
|
9
|
+
import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
10
|
+
|
|
11
|
+
function env(name, required = false) {
|
|
12
|
+
const value = String(process.env[name] || "").trim();
|
|
13
|
+
if (required && !value) throw new Error(`${name} is required`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readJson(file) {
|
|
18
|
+
return JSON.parse(fs.readFileSync(path.resolve(file), "utf8"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function packageName() {
|
|
22
|
+
const declared = env("BUILDCHAIN_PUBLISH_PACKAGE_MAIN");
|
|
23
|
+
if (declared) return declared;
|
|
24
|
+
const manifestPath = env("BUILDCHAIN_SEALED_BUNDLE_MANIFEST", true);
|
|
25
|
+
const manifest = readJson(manifestPath);
|
|
26
|
+
const selected = String(manifest?.npm?.name || "").trim();
|
|
27
|
+
if (!selected)
|
|
28
|
+
throw new Error("sealed bundle manifest does not declare npm.name");
|
|
29
|
+
return selected;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function observedVersions(name) {
|
|
33
|
+
const result = spawnSyncCommand(
|
|
34
|
+
"npm",
|
|
35
|
+
[
|
|
36
|
+
"view",
|
|
37
|
+
name,
|
|
38
|
+
"versions",
|
|
39
|
+
"--json",
|
|
40
|
+
"--registry=https://registry.npmjs.org/",
|
|
41
|
+
],
|
|
42
|
+
{ encoding: "utf8" },
|
|
43
|
+
);
|
|
44
|
+
if (result.error) throw result.error;
|
|
45
|
+
if (result.status !== 0) {
|
|
46
|
+
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
47
|
+
if (/\bE404\b|404 Not Found|is not in this registry/iu.test(output))
|
|
48
|
+
return [];
|
|
49
|
+
throw new Error(`npm version discovery failed: ${output.trim()}`);
|
|
50
|
+
}
|
|
51
|
+
const parsed = JSON.parse(String(result.stdout || "[]"));
|
|
52
|
+
return Array.isArray(parsed) ? parsed : [parsed].filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeOutput(name, value) {
|
|
56
|
+
const output = env("GITHUB_OUTPUT");
|
|
57
|
+
if (!output) return;
|
|
58
|
+
fs.appendFileSync(output, `${name}=${String(value)}\n`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolveV4ProductPublicationIntent() {
|
|
62
|
+
const name = packageName();
|
|
63
|
+
const sourceSha = env("BUILDCHAIN_SOURCE_SHA", true);
|
|
64
|
+
const manifest = readJson(env("BUILDCHAIN_SEALED_BUNDLE_MANIFEST", true));
|
|
65
|
+
const requiredArtifacts = readJson(
|
|
66
|
+
env("BUILDCHAIN_REQUIRED_ARTIFACTS_PATH", true),
|
|
67
|
+
);
|
|
68
|
+
const channel = env("BUILDCHAIN_CHANNEL", true);
|
|
69
|
+
const intent = selectV4ProductPublicationIntent({
|
|
70
|
+
channel,
|
|
71
|
+
targetRef: env("BUILDCHAIN_TARGET_REF", true),
|
|
72
|
+
sourceSha,
|
|
73
|
+
sourceTimestamp: env("BUILDCHAIN_SOURCE_TIMESTAMP", true),
|
|
74
|
+
repository: env("BUILDCHAIN_REPOSITORY", true),
|
|
75
|
+
packageName: name,
|
|
76
|
+
distTag:
|
|
77
|
+
env("BUILDCHAIN_PUBLISH_DIST_TAG") ||
|
|
78
|
+
(channel === "alpha" ? "alpha" : "latest"),
|
|
79
|
+
sealedBundleRoot: manifest.root,
|
|
80
|
+
requiredArtifactsRoot: v4ContentRoot(
|
|
81
|
+
"v4-product-required-artifacts",
|
|
82
|
+
requiredArtifacts,
|
|
83
|
+
),
|
|
84
|
+
candidateVersion: env("BUILDCHAIN_CANDIDATE_VERSION", true),
|
|
85
|
+
recoveredVersion: env("BUILDCHAIN_RECOVERED_PUBLICATION_VERSION"),
|
|
86
|
+
observedVersions: observedVersions(name),
|
|
87
|
+
});
|
|
88
|
+
const outputPath = path.resolve(
|
|
89
|
+
env("BUILDCHAIN_PRODUCT_PUBLICATION_INTENT_PATH") ||
|
|
90
|
+
".buildchain/release-candidate/v4-product-publication-intent.json",
|
|
91
|
+
);
|
|
92
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
93
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(intent, null, 2)}\n`);
|
|
94
|
+
writeOutput("version", intent.version);
|
|
95
|
+
writeOutput("exact-tag", intent.exactTag);
|
|
96
|
+
writeOutput("intent-path", outputPath);
|
|
97
|
+
writeOutput("intent-root", intent.intentRoot);
|
|
98
|
+
return { name, intent, outputPath };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
process.argv[1] &&
|
|
103
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
104
|
+
) {
|
|
105
|
+
try {
|
|
106
|
+
const result = resolveV4ProductPublicationIntent();
|
|
107
|
+
process.stdout.write(
|
|
108
|
+
`v4 product publication intent: ${result.name}@${result.intent.version} (${result.intent.mode})\n`,
|
|
109
|
+
);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`v4-product-publication-intent: ${error.message}`);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
}
|
|
114
|
+
}
|