chainlesschain 0.156.6 → 0.156.7
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 +24 -0
- package/package.json +2 -1
- package/src/assets/web-panel/assets/{ActionButton-Dme4LGax.js → ActionButton-Cs4QdjYb.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-B3-5BjRm.js → Analytics-Xot0e9TT.js} +1 -1
- package/src/assets/web-panel/assets/{AppLayout-DvVLRyPs.js → AppLayout-3qsE1-pz.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-wLmjCc9u.js → BaseInput-Tg40P4JM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BfbEUJDW.js → Checkbox-CheA2Ety.js} +1 -1
- package/src/assets/web-panel/assets/{Col-HJI40OzO.js → Col-Cdfsmnaq.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-ADVAwcbQ.js → Compact-D3LSgEpW.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-DcNB5TYu.js → Cron-9MV6k-MV.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-p_Wuj0Un.js → Dashboard-A1TZC5_t.js} +1 -1
- package/src/assets/web-panel/assets/{Dropdown-CrXGzreQ.js → Dropdown-DZxUTZvw.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-B97Dibo2.js → FormItemContext-C3_-j_SR.js} +1 -1
- package/src/assets/web-panel/assets/{Git-90CPsOOr.js → Git-Cw-gW-kh.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-B_3zNQNB.js → Memory-CMweTJyn.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DNQz9UXh.js → Notes-B_W3BfZF.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CgXUnp-W.js → Organization-Dz_jGbAM.js} +1 -1
- package/src/assets/web-panel/assets/{Overflow-BVsn6SM5.js → Overflow-Dka3nWV9.js} +1 -1
- package/src/assets/web-panel/assets/{Permissions-DIFqcnjU.js → Permissions-DvXVIlHX.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-mscN7CK5.js → Providers-DUfX_ynl.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BFUWxIkx.js → Row-DZhDSo2Q.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dpa4h-q_.js → RssFeed-CHQpUl3h.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DR6HKo_S.js → Security-FUSOn89T.js} +1 -1
- package/src/assets/web-panel/assets/{Skeleton-VNikEgM4.js → Skeleton-DxmZ7zRw.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Ny_4GO6a.js → Templates-BVbmyn38.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-C7MTh_xj.js → Trigger-xAvohiq9.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BNRFHgJ9.js → VideoEditing-BUWYQv2y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BUfg4IAx.js → Wallet-BDYdEwFf.js} +1 -1
- package/src/assets/web-panel/assets/{WebAuthn-Cia89OyQ.js → WebAuthn-CvpuagtK.js} +1 -1
- package/src/assets/web-panel/assets/{WorkflowEditor-C1OsMtqv.js → WorkflowEditor-BR7W5cjw.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C_wDMX2Q.js → colors-C5kDbQCi.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C1ikzEN-.js → compact-item-Bo_1zDrX.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-XExBTk9v.js → createContext-CniPpJsG.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-mXvd_Kdq.js → hasIn-ClDc6Sz8.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6Hyy0Bc.js → index--lcO-bOn.js} +1 -1
- package/src/assets/web-panel/assets/{index-lPIeHtHE.js → index-6tQekF0Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfncNR8d.js → index-8qWxPHSb.js} +1 -1
- package/src/assets/web-panel/assets/{index-C53dnYiq.js → index-B7nGNm_C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMYADk0v.js → index-B8y0NO-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMcLOtIo.js → index-BAlSSCbs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJkIheU6.js → index-BCXFoTAw.js} +1 -1
- package/src/assets/web-panel/assets/{index-kLUQdSDJ.js → index-BHruTebo.js} +1 -1
- package/src/assets/web-panel/assets/{index-1ZqkTPt2.js → index-BJx6C3J8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbpKJ2W0.js → index-BUTCJTbj.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6P9mWuk.js → index-BYShDlZ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9tzxSFs.js → index-Bv2OmZAS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFFb9yPd.js → index-C92K4iDE.js} +1 -1
- package/src/assets/web-panel/assets/{index-v4Oi0d0l.js → index-CCdb36il.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-TI0cZ2.js → index-CKR2ITFk.js} +1 -1
- package/src/assets/web-panel/assets/{index-fLUJs2Sr.js → index-CMcGcbea.js} +1 -1
- package/src/assets/web-panel/assets/{index-BirLVqrC.js → index-CTetsi8W.js} +1 -1
- package/src/assets/web-panel/assets/{index-LpE6Six-.js → index-CXxLp7Aw.js} +1 -1
- package/src/assets/web-panel/assets/{index-qtDQSqTG.js → index-CeSV8f3b.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwMlStra.js → index-Ch5mAXeh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOqmUcij.js → index-CwhWEkmA.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_oSE2Nk.js → index-D2fe9a6f.js} +1 -1
- package/src/assets/web-panel/assets/index-D3UDIt7h.js +1 -0
- package/src/assets/web-panel/assets/{index-CxwU-EjS.js → index-D90sLw5Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1eekAaa.js → index-D9bolkbl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BL27IhbN.js → index-DNY0K7iI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du7KGlCP.js → index-DSiHmo4b.js} +1 -1
- package/src/assets/web-panel/assets/{index-CttcpCq_.js → index-DTYnvYqB.js} +1 -1
- package/src/assets/web-panel/assets/{index-jg5cpQg9.js → index-DaLYbr0E.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYLE4bnY.js → index-DkSNIJhM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZjQgmBq.js → index-DnQkqOZj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaMG8ksh.js → index-Dn_OQQaV.js} +3 -3
- package/src/assets/web-panel/assets/{index-Dz6RDRcu.js → index-Dtfrhky9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTle6zcb.js → index-JNbd08FN.js} +1 -1
- package/src/assets/web-panel/assets/index-PT376OZM.js +1 -0
- package/src/assets/web-panel/assets/{index-BBOVB9YK.js → index-cIgCeEqo.js} +1 -1
- package/src/assets/web-panel/assets/{index-a0qENb5U.js → index-vBi4x_6g.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5Zv4fBx.js → index-xL8gcpmy.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DOj2K4bh.js → initDefaultProps-DMfJaUzk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-joGf7r-l.js → motion-sEbWmOWo.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cwb6tumJ.js → move-DIWXVs--.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CPycjJ8C.js → omit-D7mkMPhu.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CnibXC3T.js → pickAttrs-B25NUX4k.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DWcIO1y4.js → placementArrow-By1Bkq1d.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C5giLhLf.js → responsiveObserve-B3aCQz5r.js} +1 -1
- package/src/assets/web-panel/assets/{slide-zwgmm7vM.js → slide-eR-f56FQ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CK8tJSHq.js → statusUtils-zcNWczhN.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BzLSEXyu.js → styleChecker-u9Z0IfRy.js} +1 -1
- package/src/assets/web-panel/assets/{transition-D4AbuDdO.js → transition-gRK4XSlW.js} +1 -1
- package/src/assets/web-panel/assets/{useConfigInject-ImjEZhXr.js → useConfigInject-ZEunuNHN.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Cd-PoTMl.js → useFlexGapSupport-BpbEJfeh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DAWimP6X.js → vnode-DVHvXn9F.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-u6SXbmzZ.js → zoom-ByzgJIn6.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/pack.js +463 -0
- package/src/commands/ui.js +6 -0
- package/src/gateways/ws/ws-server.js +29 -3
- package/src/index.js +34 -1
- package/src/lib/governance-v2-helpers.js +109 -0
- package/src/lib/packer/config-template-builder.js +151 -0
- package/src/lib/packer/errors.js +30 -0
- package/src/lib/packer/index.js +383 -0
- package/src/lib/packer/manifest-writer.js +93 -0
- package/src/lib/packer/native-prebuild-collector.js +305 -0
- package/src/lib/packer/pack-update-applier.js +203 -0
- package/src/lib/packer/pack-update-checker.js +185 -0
- package/src/lib/packer/pack-update-downloader.js +162 -0
- package/src/lib/packer/pkg-config-generator.js +325 -0
- package/src/lib/packer/pkg-runner.js +139 -0
- package/src/lib/packer/precheck.js +273 -0
- package/src/lib/packer/project-assets-collector.js +0 -0
- package/src/lib/packer/smoke-runner.js +339 -0
- package/src/lib/packer/web-panel-builder.js +157 -0
- package/src/lib/web-ui-server.js +95 -2
- package/src/lib/ws-server.js +1 -0
- package/src/runtime/agent-runtime.js +1 -0
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/assets/web-panel/assets/index-DQgS_8Fd.js +0 -1
- package/src/assets/web-panel/assets/index-f4W8Sok0.js +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5b: stream a packed exe to disk and verify its SHA-256 against the
|
|
3
|
+
* manifest. Self-replacement is Phase 5c; this module just writes the new
|
|
4
|
+
* artifact next to the old one (conventionally `<exePath>.new`).
|
|
5
|
+
*
|
|
6
|
+
* Design notes:
|
|
7
|
+
* - Streaming, not buffered: artifacts are ~80-140 MB, so we write chunks
|
|
8
|
+
* straight to disk and hash incrementally. Node's fetch yields a web
|
|
9
|
+
* ReadableStream, which we consume via its async iterator.
|
|
10
|
+
* - Atomicity: we write to `<outputPath>.partial` and rename on success.
|
|
11
|
+
* A SHA mismatch or mid-stream failure deletes the partial so a retry
|
|
12
|
+
* starts from scratch.
|
|
13
|
+
* - No resume: first cut skips Range / If-Range. Large-artifact resume
|
|
14
|
+
* is a Phase 5d concern if it becomes painful.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import crypto from "node:crypto";
|
|
20
|
+
|
|
21
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} ctx
|
|
25
|
+
* @param {string} ctx.url absolute http(s) URL to the artifact
|
|
26
|
+
* @param {string} ctx.sha256 lowercase hex SHA-256 from the manifest
|
|
27
|
+
* @param {string} ctx.outputPath where the verified artifact lands
|
|
28
|
+
* @param {typeof fetch} [ctx.fetchImpl] injected for tests
|
|
29
|
+
* @param {(p:{bytes:number,total:number|null})=>void} [ctx.onProgress]
|
|
30
|
+
* fires as chunks arrive; total
|
|
31
|
+
* is null if Content-Length was
|
|
32
|
+
* absent or unparseable
|
|
33
|
+
* @returns {Promise<{outputPath:string,bytes:number,sha256:string}>}
|
|
34
|
+
*/
|
|
35
|
+
export async function downloadAndVerify(ctx) {
|
|
36
|
+
const { url, sha256, outputPath, fetchImpl = fetch, onProgress } = ctx;
|
|
37
|
+
|
|
38
|
+
if (!url || typeof url !== "string") {
|
|
39
|
+
throw new DownloadError("url is required", "NO_URL");
|
|
40
|
+
}
|
|
41
|
+
if (!sha256 || !SHA256_HEX.test(sha256)) {
|
|
42
|
+
throw new DownloadError(
|
|
43
|
+
`sha256 must be a 64-char lowercase hex string (got ${JSON.stringify(sha256)})`,
|
|
44
|
+
"BAD_SHA256",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (!outputPath || typeof outputPath !== "string") {
|
|
48
|
+
throw new DownloadError("outputPath is required", "NO_OUTPUT");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
52
|
+
const partialPath = outputPath + ".partial";
|
|
53
|
+
|
|
54
|
+
// Clean up any stale partial from a prior interrupted attempt. Ignoring
|
|
55
|
+
// ENOENT is fine — we just want to start with a blank slate.
|
|
56
|
+
try {
|
|
57
|
+
fs.unlinkSync(partialPath);
|
|
58
|
+
} catch {
|
|
59
|
+
/* no prior partial */
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let response;
|
|
63
|
+
try {
|
|
64
|
+
response = await fetchImpl(url, { headers: { Accept: "*/*" } });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new DownloadError(
|
|
67
|
+
`fetch failed: ${err.message}`,
|
|
68
|
+
err?.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new DownloadError(
|
|
73
|
+
`artifact fetch failed: HTTP ${response.status}`,
|
|
74
|
+
"FETCH_FAILED",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const totalRaw = response.headers?.get?.("content-length");
|
|
79
|
+
const total = totalRaw ? Number(totalRaw) : null;
|
|
80
|
+
const body = response.body;
|
|
81
|
+
if (!body) {
|
|
82
|
+
throw new DownloadError(
|
|
83
|
+
"response has no body stream (fetch impl returned no body)",
|
|
84
|
+
"NO_BODY",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const hasher = crypto.createHash("sha256");
|
|
89
|
+
const out = fs.createWriteStream(partialPath);
|
|
90
|
+
let bytes = 0;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Web ReadableStream exposes an async iterator in Node ≥18. We prefer
|
|
94
|
+
// that over pumping via pipe() because we need to see each chunk for
|
|
95
|
+
// hashing + progress, and we want the final rename deterministic.
|
|
96
|
+
for await (const chunk of body) {
|
|
97
|
+
// `chunk` is a Uint8Array
|
|
98
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
99
|
+
hasher.update(buf);
|
|
100
|
+
bytes += buf.length;
|
|
101
|
+
const ok = out.write(buf);
|
|
102
|
+
if (!ok) {
|
|
103
|
+
await new Promise((r) => out.once("drain", r));
|
|
104
|
+
}
|
|
105
|
+
if (typeof onProgress === "function") {
|
|
106
|
+
try {
|
|
107
|
+
onProgress({ bytes, total: Number.isFinite(total) ? total : null });
|
|
108
|
+
} catch {
|
|
109
|
+
/* progress callback errors must not interrupt the download */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await new Promise((resolve, reject) => {
|
|
114
|
+
out.end((err) => (err ? reject(err) : resolve()));
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
try {
|
|
118
|
+
out.destroy();
|
|
119
|
+
fs.unlinkSync(partialPath);
|
|
120
|
+
} catch {
|
|
121
|
+
/* best effort */
|
|
122
|
+
}
|
|
123
|
+
throw new DownloadError(`stream aborted: ${err.message}`, "STREAM_ERROR");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const actualSha = hasher.digest("hex");
|
|
127
|
+
if (actualSha !== sha256.toLowerCase()) {
|
|
128
|
+
try {
|
|
129
|
+
fs.unlinkSync(partialPath);
|
|
130
|
+
} catch {
|
|
131
|
+
/* best effort */
|
|
132
|
+
}
|
|
133
|
+
throw new DownloadError(
|
|
134
|
+
`SHA-256 mismatch: expected ${sha256}, got ${actualSha}`,
|
|
135
|
+
"SHA_MISMATCH",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Atomic rename — on Windows this fails if the destination exists; delete
|
|
140
|
+
// first. On POSIX rename atomically replaces, but the extra unlink is
|
|
141
|
+
// harmless and keeps behavior symmetric across platforms.
|
|
142
|
+
try {
|
|
143
|
+
fs.unlinkSync(outputPath);
|
|
144
|
+
} catch {
|
|
145
|
+
/* no prior artifact */
|
|
146
|
+
}
|
|
147
|
+
fs.renameSync(partialPath, outputPath);
|
|
148
|
+
|
|
149
|
+
return { outputPath, bytes, sha256: actualSha };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Typed error with a machine-readable `code`. The `cc pack check-update
|
|
154
|
+
* --download` command turns these into friendly messages + exit codes.
|
|
155
|
+
*/
|
|
156
|
+
export class DownloadError extends Error {
|
|
157
|
+
constructor(message, code) {
|
|
158
|
+
super(message);
|
|
159
|
+
this.name = "DownloadError";
|
|
160
|
+
this.code = code;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5: pkg-config-generator
|
|
3
|
+
*
|
|
4
|
+
* Produce a transient pkg config (written next to a temp entry script) that
|
|
5
|
+
* tells @yao-pkg/pkg which scripts to compile and which assets to embed.
|
|
6
|
+
*
|
|
7
|
+
* The pkg config used here is the standard "pkg" key shape:
|
|
8
|
+
* {
|
|
9
|
+
* "scripts": ["src/**\/*.js"],
|
|
10
|
+
* "assets": ["src/assets/web-panel/**\/*", "templates/**\/*", "prebuilds/**\/*"],
|
|
11
|
+
* "targets": ["node20-win-x64"],
|
|
12
|
+
* "outputPath": "dist"
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* We do NOT mutate the real packages/cli/package.json — we write a synthesized
|
|
16
|
+
* package.json into the build temp dir that re-exports the bin and adds the
|
|
17
|
+
* "pkg" key. pkg is invoked against that file.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} ctx
|
|
25
|
+
* @param {string} ctx.cliRoot packages/cli/
|
|
26
|
+
* @param {string} ctx.tempDir build temp dir
|
|
27
|
+
* @param {string} ctx.distDir web-panel dist absolute path
|
|
28
|
+
* @param {string|null} ctx.prebuildsDir prebuilds/ absolute path or null
|
|
29
|
+
* @param {string} ctx.templatesDir templates/ absolute path
|
|
30
|
+
* @param {string[]} ctx.targets
|
|
31
|
+
* @param {string} ctx.outputPath absolute output path (no ext)
|
|
32
|
+
* @param {boolean} ctx.compress
|
|
33
|
+
* @param {object} [ctx.runtime] defaults baked into the entry
|
|
34
|
+
* @param {string} [ctx.runtime.token] 'auto' = regenerate each run,
|
|
35
|
+
* '' = no auth, any other string
|
|
36
|
+
* is hardcoded.
|
|
37
|
+
* @param {string} [ctx.runtime.bindHost] default --host for `ui`
|
|
38
|
+
* @param {number} [ctx.runtime.wsPort] default --ws-port
|
|
39
|
+
* @param {number} [ctx.runtime.uiPort] default --port
|
|
40
|
+
* @param {object|null} [ctx.project] project-mode payload from
|
|
41
|
+
* project-assets-collector. When
|
|
42
|
+
* present, the entry script
|
|
43
|
+
* materializes the bundled
|
|
44
|
+
* .chainlesschain/ to a user-data
|
|
45
|
+
* dir and chdirs into it.
|
|
46
|
+
* @param {string} [ctx.project.projectDir] tempDir/project absolute path
|
|
47
|
+
* @param {string} [ctx.project.projectName]
|
|
48
|
+
* @param {string} [ctx.project.configSha]
|
|
49
|
+
* @param {string} [ctx.projectEntry] default subcommand for project
|
|
50
|
+
* mode (e.g. 'ui', 'chat'). If
|
|
51
|
+
* omitted, reads from the bundled
|
|
52
|
+
* config's `pack.entry` field.
|
|
53
|
+
* @param {boolean} [ctx.forceRefreshOnLaunch] re-materialize every launch
|
|
54
|
+
* @param {string|null} [ctx.updateManifestUrl] Phase 5a OTA manifest URL;
|
|
55
|
+
* baked into BAKED.updateManifestUrl
|
|
56
|
+
* and surfaced via `cc pack check-update`
|
|
57
|
+
* @returns {{ pkgConfigDir: string, pkgConfigFile: string, entryScript: string, projectMeta: object|null }}
|
|
58
|
+
*/
|
|
59
|
+
export function generatePkgConfig(ctx) {
|
|
60
|
+
const {
|
|
61
|
+
cliRoot,
|
|
62
|
+
tempDir,
|
|
63
|
+
distDir,
|
|
64
|
+
prebuildsDir,
|
|
65
|
+
templatesDir,
|
|
66
|
+
targets,
|
|
67
|
+
outputPath,
|
|
68
|
+
compress,
|
|
69
|
+
runtime = {},
|
|
70
|
+
project = null,
|
|
71
|
+
projectEntry,
|
|
72
|
+
forceRefreshOnLaunch = false,
|
|
73
|
+
updateManifestUrl = null,
|
|
74
|
+
} = ctx;
|
|
75
|
+
|
|
76
|
+
const bakedTokenMode =
|
|
77
|
+
typeof runtime.token === "string" ? runtime.token : "auto";
|
|
78
|
+
const bakedHost =
|
|
79
|
+
typeof runtime.bindHost === "string" && runtime.bindHost
|
|
80
|
+
? runtime.bindHost
|
|
81
|
+
: "127.0.0.1";
|
|
82
|
+
const bakedWsPort = Number.isFinite(runtime.wsPort)
|
|
83
|
+
? String(runtime.wsPort)
|
|
84
|
+
: "18800";
|
|
85
|
+
const bakedUiPort = Number.isFinite(runtime.uiPort)
|
|
86
|
+
? String(runtime.uiPort)
|
|
87
|
+
: "18810";
|
|
88
|
+
|
|
89
|
+
// ── Project mode: read pack.* from bundled config ─────────────────────────
|
|
90
|
+
let projectBaked = null;
|
|
91
|
+
if (project) {
|
|
92
|
+
const bundledConfigPath = path.join(
|
|
93
|
+
project.projectDir,
|
|
94
|
+
".chainlesschain",
|
|
95
|
+
"config.json",
|
|
96
|
+
);
|
|
97
|
+
let packConfig = {};
|
|
98
|
+
try {
|
|
99
|
+
const raw = JSON.parse(fs.readFileSync(bundledConfigPath, "utf-8"));
|
|
100
|
+
packConfig = raw.pack || {};
|
|
101
|
+
} catch {
|
|
102
|
+
/* already validated by Phase 3.5 — missing config falls back to defaults */
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resolvedEntry =
|
|
106
|
+
typeof projectEntry === "string" && projectEntry.trim()
|
|
107
|
+
? projectEntry.trim()
|
|
108
|
+
: typeof packConfig.entry === "string" && packConfig.entry.trim()
|
|
109
|
+
? packConfig.entry.trim()
|
|
110
|
+
: "ui";
|
|
111
|
+
|
|
112
|
+
projectBaked = {
|
|
113
|
+
projectMode: true,
|
|
114
|
+
projectName: project.projectName,
|
|
115
|
+
projectEntry: resolvedEntry,
|
|
116
|
+
projectConfigSha: project.configSha,
|
|
117
|
+
projectAutoPersona:
|
|
118
|
+
typeof packConfig.autoPersona === "string"
|
|
119
|
+
? packConfig.autoPersona
|
|
120
|
+
: null,
|
|
121
|
+
projectAllowedSubcommands: Array.isArray(packConfig.allowedSubcommands)
|
|
122
|
+
? packConfig.allowedSubcommands
|
|
123
|
+
: ["ui", "chat", "agent", "skill"],
|
|
124
|
+
// Absolute path to the bundled .chainlesschain/ inside the pkg snapshot FS.
|
|
125
|
+
// At runtime, pkg surfaces assets at their original absolute paths.
|
|
126
|
+
projectBundledDir: posixify(
|
|
127
|
+
path.join(project.projectDir, ".chainlesschain"),
|
|
128
|
+
),
|
|
129
|
+
forceRefreshOnLaunch: Boolean(forceRefreshOnLaunch),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const pkgConfigDir = path.join(tempDir, "pkg-config");
|
|
134
|
+
fs.mkdirSync(pkgConfigDir, { recursive: true });
|
|
135
|
+
|
|
136
|
+
// Read the real CLI package.json to inherit version/dependencies
|
|
137
|
+
const realPkg = JSON.parse(
|
|
138
|
+
fs.readFileSync(path.join(cliRoot, "package.json"), "utf-8"),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Phase 5a: bake OTA manifest URL + the exact CLI version shipped in this
|
|
142
|
+
// artifact so `cc pack check-update` can compare against the manifest's
|
|
143
|
+
// latest.cliVersion without relying on the unpacked CLI's VERSION constant.
|
|
144
|
+
const bakedObj = {
|
|
145
|
+
tokenMode: bakedTokenMode,
|
|
146
|
+
host: bakedHost,
|
|
147
|
+
wsPort: bakedWsPort,
|
|
148
|
+
uiPort: bakedUiPort,
|
|
149
|
+
packedCliVersion: realPkg.version || null,
|
|
150
|
+
updateManifestUrl:
|
|
151
|
+
typeof updateManifestUrl === "string" && updateManifestUrl
|
|
152
|
+
? updateManifestUrl
|
|
153
|
+
: null,
|
|
154
|
+
...(projectBaked || {}),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Inline the real bin's logic directly. We can't use `import('...')`
|
|
158
|
+
// because pkg's snapshot bootstrap does not register a dynamic-import
|
|
159
|
+
// callback (ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING). Static ESM
|
|
160
|
+
// imports compile fine via yao-pkg's ESM mode.
|
|
161
|
+
const entryScript = path.join(pkgConfigDir, "pack-entry.js");
|
|
162
|
+
const ensureUtf8Path = posixify(
|
|
163
|
+
path.join(cliRoot, "src", "lib", "ensure-utf8.js"),
|
|
164
|
+
);
|
|
165
|
+
const indexPath = posixify(path.join(cliRoot, "src", "index.js"));
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
entryScript,
|
|
168
|
+
[
|
|
169
|
+
"#!/usr/bin/env node",
|
|
170
|
+
"// pkg entry — inlines the real CLI bin (see bin/chainlesschain.js).",
|
|
171
|
+
"// CC_PACK_MODE flips the WS layer to allow chat/agent over execute.",
|
|
172
|
+
"process.env.CC_PACK_MODE = '1';",
|
|
173
|
+
"import crypto from 'node:crypto';",
|
|
174
|
+
"import fs from 'node:fs';",
|
|
175
|
+
"import os from 'node:os';",
|
|
176
|
+
"import path from 'node:path';",
|
|
177
|
+
`import { ensureUtf8 } from '${ensureUtf8Path}';`,
|
|
178
|
+
`import { createProgram } from '${indexPath}';`,
|
|
179
|
+
"ensureUtf8();",
|
|
180
|
+
"// Build-time defaults baked by the packer. Each is overridable at",
|
|
181
|
+
"// runtime either via the matching env var or by passing the flag.",
|
|
182
|
+
`const BAKED = Object.freeze(${JSON.stringify(bakedObj)});`,
|
|
183
|
+
"// Expose BAKED on globalThis so downstream subcommands (e.g. `cc pack",
|
|
184
|
+
"// check-update`) can read packedCliVersion / updateManifestUrl without",
|
|
185
|
+
"// importing from the packer. No-op in CLI-only builds outside pkg.",
|
|
186
|
+
"globalThis.BAKED = BAKED;",
|
|
187
|
+
"// Merge-copy helper: copies src tree into dest, skipping files that",
|
|
188
|
+
"// already exist (preserves user modifications). Used only in project mode.",
|
|
189
|
+
"function copyRecursiveMerge(src, dest) {",
|
|
190
|
+
" if (!fs.existsSync(src)) return;",
|
|
191
|
+
" fs.mkdirSync(dest, { recursive: true });",
|
|
192
|
+
" for (const e of fs.readdirSync(src, { withFileTypes: true })) {",
|
|
193
|
+
" const sp = path.join(src, e.name), dp = path.join(dest, e.name);",
|
|
194
|
+
" if (e.isDirectory()) { copyRecursiveMerge(sp, dp); }",
|
|
195
|
+
" else if (!fs.existsSync(dp)) { fs.copyFileSync(sp, dp); }",
|
|
196
|
+
" else { console.warn('[cc-pack] Keeping existing file (user-modified):', dp); }",
|
|
197
|
+
" }",
|
|
198
|
+
"}",
|
|
199
|
+
"// ── Project mode: materialize bundled .chainlesschain/ to user-data dir ──",
|
|
200
|
+
"// BAKED.projectMode is absent (falsy) in CLI-only builds — entire block skipped.",
|
|
201
|
+
"if (BAKED.projectMode) {",
|
|
202
|
+
" const _userDataDir = path.join(",
|
|
203
|
+
" os.homedir(), '.chainlesschain-projects',",
|
|
204
|
+
" `${BAKED.projectName}-${BAKED.projectConfigSha.slice(0, 8)}`,",
|
|
205
|
+
" );",
|
|
206
|
+
" const _markerFile = path.join(_userDataDir, '.chainlesschain', '.pack-version');",
|
|
207
|
+
" const _needsMaterialize =",
|
|
208
|
+
" BAKED.forceRefreshOnLaunch ||",
|
|
209
|
+
" !fs.existsSync(_markerFile) ||",
|
|
210
|
+
" fs.readFileSync(_markerFile, 'utf8').trim() !== BAKED.projectConfigSha;",
|
|
211
|
+
" if (_needsMaterialize) {",
|
|
212
|
+
" copyRecursiveMerge(BAKED.projectBundledDir, path.join(_userDataDir, '.chainlesschain'));",
|
|
213
|
+
" fs.mkdirSync(path.dirname(_markerFile), { recursive: true });",
|
|
214
|
+
" fs.writeFileSync(_markerFile, BAKED.projectConfigSha, 'utf8');",
|
|
215
|
+
" }",
|
|
216
|
+
" process.env.CC_PROJECT_ROOT = _userDataDir;",
|
|
217
|
+
" if (BAKED.projectAllowedSubcommands) {",
|
|
218
|
+
" process.env.CC_PROJECT_ALLOWED_SUBCOMMANDS = BAKED.projectAllowedSubcommands.join(',');",
|
|
219
|
+
" }",
|
|
220
|
+
" // Phase 3b: expose the baked auto-persona via env so downstream",
|
|
221
|
+
" // consumers (skill-loader / persona resolver) can pick it up.",
|
|
222
|
+
" if (BAKED.projectAutoPersona) {",
|
|
223
|
+
" process.env.CC_PACK_AUTO_PERSONA = BAKED.projectAutoPersona;",
|
|
224
|
+
" }",
|
|
225
|
+
"}",
|
|
226
|
+
"// Double-click from Explorer arrives with no subcommand — without",
|
|
227
|
+
"// this default, commander prints help and exits, which looks like",
|
|
228
|
+
"// a black console 'flash and close'. In project mode the baked",
|
|
229
|
+
"// entry subcommand is used instead of the CLI-only 'ui' fallback.",
|
|
230
|
+
"const _rest = process.argv.slice(2);",
|
|
231
|
+
"const _hasSub = _rest.some((a) => a && !a.startsWith('-'));",
|
|
232
|
+
"const _argSet = new Set(_rest);",
|
|
233
|
+
"const _hasFlag = (...names) => names.some((n) => _argSet.has(n));",
|
|
234
|
+
"// Commander short-circuits --version / --help before running any",
|
|
235
|
+
"// command — don't pollute their stdout with the token banner and",
|
|
236
|
+
"// baked defaults they'd never see anyway.",
|
|
237
|
+
"const _shortCircuits = _hasFlag('-v', '--version', '-h', '--help');",
|
|
238
|
+
"if (!_hasSub && !_shortCircuits) {",
|
|
239
|
+
" const _entryCmd = BAKED.projectMode ? BAKED.projectEntry : 'ui';",
|
|
240
|
+
" for (const _p of _entryCmd.split(/\\s+/).filter(Boolean)) process.argv.push(_p);",
|
|
241
|
+
" if (_entryCmd.split(/\\s+/)[0] === 'ui') {",
|
|
242
|
+
" if (!_hasFlag('-p', '--port'))",
|
|
243
|
+
" process.argv.push('--port', process.env.CC_PACK_UI_PORT || BAKED.uiPort);",
|
|
244
|
+
" if (!_hasFlag('--ws-port'))",
|
|
245
|
+
" process.argv.push('--ws-port', process.env.CC_PACK_WS_PORT || BAKED.wsPort);",
|
|
246
|
+
" if (!_hasFlag('-H', '--host'))",
|
|
247
|
+
" process.argv.push('--host', process.env.CC_PACK_HOST || BAKED.host);",
|
|
248
|
+
" if (!_hasFlag('--token') && BAKED.tokenMode) {",
|
|
249
|
+
" let tok = process.env.CC_PACK_TOKEN || BAKED.tokenMode;",
|
|
250
|
+
" if (tok === 'auto') {",
|
|
251
|
+
" tok = crypto.randomBytes(16).toString('hex');",
|
|
252
|
+
" console.log('');",
|
|
253
|
+
" console.log(' \\u2139 Access token (auto-generated this run):');",
|
|
254
|
+
" console.log(' ' + tok);",
|
|
255
|
+
" console.log(' set CC_PACK_TOKEN=<value> to pin a stable token.');",
|
|
256
|
+
" console.log('');",
|
|
257
|
+
" }",
|
|
258
|
+
" process.argv.push('--token', tok);",
|
|
259
|
+
" }",
|
|
260
|
+
" }",
|
|
261
|
+
"}",
|
|
262
|
+
"// When launched from Explorer, Node exits as soon as the event loop",
|
|
263
|
+
"// drains. If something throws before the UI server starts listening,",
|
|
264
|
+
"// we want the user to see the error — pause on fatal errors.",
|
|
265
|
+
"process.on('uncaughtException', (err) => {",
|
|
266
|
+
" console.error('\\n[cc-pack] Fatal error:', err && err.stack || err);",
|
|
267
|
+
" if (process.stdin.isTTY) {",
|
|
268
|
+
" console.error('\\nPress any key to exit...');",
|
|
269
|
+
" try { process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.once('data', () => process.exit(1)); } catch { process.exit(1); }",
|
|
270
|
+
" } else { process.exit(1); }",
|
|
271
|
+
"});",
|
|
272
|
+
"const program = createProgram();",
|
|
273
|
+
"program.parse(process.argv);",
|
|
274
|
+
"",
|
|
275
|
+
].join("\n"),
|
|
276
|
+
"utf-8",
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const assets = [
|
|
280
|
+
`${posixify(distDir)}/**/*`,
|
|
281
|
+
`${posixify(templatesDir)}/**/*`,
|
|
282
|
+
];
|
|
283
|
+
if (prebuildsDir) assets.push(`${posixify(prebuildsDir)}/**/*`);
|
|
284
|
+
// Project mode: bundle the collected .chainlesschain/ snapshot as an asset.
|
|
285
|
+
// At runtime, pkg surfaces it at its original absolute path in the snapshot FS.
|
|
286
|
+
if (project) assets.push(`${posixify(project.projectDir)}/**/*`);
|
|
287
|
+
|
|
288
|
+
const synthesizedPkg = {
|
|
289
|
+
name: realPkg.name + "-pack",
|
|
290
|
+
version: realPkg.version,
|
|
291
|
+
bin: "pack-entry.js",
|
|
292
|
+
type: "module",
|
|
293
|
+
pkg: {
|
|
294
|
+
scripts: [
|
|
295
|
+
// Glob covers the entire CLI source tree, since dependencies are
|
|
296
|
+
// resolved from cliRoot/node_modules.
|
|
297
|
+
`${posixify(cliRoot)}/src/**/*.js`,
|
|
298
|
+
`${posixify(cliRoot)}/src/**/*.cjs`,
|
|
299
|
+
`${posixify(cliRoot)}/bin/**/*.js`,
|
|
300
|
+
],
|
|
301
|
+
assets,
|
|
302
|
+
targets,
|
|
303
|
+
outputPath: path.dirname(outputPath),
|
|
304
|
+
compress: compress ? "GZip" : "None",
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const pkgConfigFile = path.join(pkgConfigDir, "package.json");
|
|
309
|
+
fs.writeFileSync(
|
|
310
|
+
pkgConfigFile,
|
|
311
|
+
JSON.stringify(synthesizedPkg, null, 2),
|
|
312
|
+
"utf-8",
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
pkgConfigDir,
|
|
317
|
+
pkgConfigFile,
|
|
318
|
+
entryScript,
|
|
319
|
+
projectMeta: projectBaked,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function posixify(p) {
|
|
324
|
+
return p.replace(/\\/g, "/");
|
|
325
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 6: pkg-runner
|
|
3
|
+
*
|
|
4
|
+
* Spawn @yao-pkg/pkg with the synthesized config and capture output.
|
|
5
|
+
*
|
|
6
|
+
* Phase 1 implementation: prefer the locally-installed binary at
|
|
7
|
+
* node_modules/.bin/pkg (or @yao-pkg/pkg/lib-es5/bin.js). If pkg is not
|
|
8
|
+
* installed, throw a clear error pointing at `npm install -D @yao-pkg/pkg`.
|
|
9
|
+
*
|
|
10
|
+
* We deliberately avoid bundling pkg as a runtime dep — it's a dev tool and
|
|
11
|
+
* users who never run `cc pack` should not pay its install cost.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { PackError, EXIT } from "./errors.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {object} ctx
|
|
22
|
+
* @param {string} ctx.cliRoot
|
|
23
|
+
* @param {string} ctx.pkgConfigFile synthesized package.json path
|
|
24
|
+
* @param {string} ctx.outputPath absolute final output path (no ext)
|
|
25
|
+
* @param {string[]} ctx.targets
|
|
26
|
+
* @param {object} [ctx.logger]
|
|
27
|
+
* @returns {{ outputs: string[] }}
|
|
28
|
+
*/
|
|
29
|
+
export function runPkg(ctx) {
|
|
30
|
+
const { cliRoot, pkgConfigFile, outputPath, targets, logger } = ctx;
|
|
31
|
+
const log = logger?.log || (() => {});
|
|
32
|
+
|
|
33
|
+
const pkgBin = locatePkgBinary(cliRoot);
|
|
34
|
+
if (!pkgBin) {
|
|
35
|
+
throw new PackError(
|
|
36
|
+
"@yao-pkg/pkg not found in node_modules. Install it as a devDependency:\n" +
|
|
37
|
+
" npm install -D @yao-pkg/pkg --workspace packages/cli",
|
|
38
|
+
EXIT.PKG,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Use --output to control the produced filename; pkg auto-appends platform
|
|
43
|
+
// suffix only when multiple targets are passed.
|
|
44
|
+
const args = [
|
|
45
|
+
pkgBin.script,
|
|
46
|
+
pkgConfigFile,
|
|
47
|
+
"--targets",
|
|
48
|
+
targets.join(","),
|
|
49
|
+
"--output",
|
|
50
|
+
outputPath,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
log(` [pkg] Running: ${pkgBin.runtime} ${args.join(" ")}`);
|
|
54
|
+
const res = spawnSync(pkgBin.runtime, args, {
|
|
55
|
+
cwd: path.dirname(pkgConfigFile),
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
windowsHide: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (res.status !== 0) {
|
|
61
|
+
throw new PackError(
|
|
62
|
+
`pkg exited with code ${res.status}. See output above for details.`,
|
|
63
|
+
EXIT.PKG,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const outputs = collectOutputs(outputPath, targets);
|
|
68
|
+
if (outputs.length === 0) {
|
|
69
|
+
throw new PackError(
|
|
70
|
+
`pkg reported success but no output file found at ${outputPath}*`,
|
|
71
|
+
EXIT.PKG,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
// Pair each produced artifact with the pkg target it came from. For a
|
|
75
|
+
// single target build, pkg writes to the bare --output path; for multi
|
|
76
|
+
// target it suffixes with "-<platformKey>". We match on the suffix
|
|
77
|
+
// when possible and fall back to positional pairing.
|
|
78
|
+
const rich = outputs.map((p) => {
|
|
79
|
+
const base = path.basename(outputPath);
|
|
80
|
+
const name = path.basename(p, path.extname(p));
|
|
81
|
+
const suffix = name.startsWith(base + "-")
|
|
82
|
+
? name.slice(base.length + 1)
|
|
83
|
+
: null;
|
|
84
|
+
let target = null;
|
|
85
|
+
if (suffix && targets.length > 1) {
|
|
86
|
+
target = targets.find((t) => t.endsWith(suffix)) || null;
|
|
87
|
+
}
|
|
88
|
+
if (!target && targets.length === 1) target = targets[0];
|
|
89
|
+
return { path: p, target };
|
|
90
|
+
});
|
|
91
|
+
return { outputs: rich };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Locate a runnable pkg binary using node's resolution algorithm so we
|
|
96
|
+
* find both local and monorepo-hoisted installs of @yao-pkg/pkg (or
|
|
97
|
+
* legacy vercel/pkg) without hard-coding paths.
|
|
98
|
+
*/
|
|
99
|
+
function locatePkgBinary(cliRoot) {
|
|
100
|
+
const req = createRequire(path.join(cliRoot, "package.json"));
|
|
101
|
+
for (const pkgName of ["@yao-pkg/pkg", "pkg"]) {
|
|
102
|
+
try {
|
|
103
|
+
const pkgJson = req.resolve(`${pkgName}/package.json`);
|
|
104
|
+
const moduleDir = path.dirname(pkgJson);
|
|
105
|
+
const meta = JSON.parse(fs.readFileSync(pkgJson, "utf-8"));
|
|
106
|
+
const binEntry = typeof meta.bin === "string" ? meta.bin : meta.bin?.pkg;
|
|
107
|
+
if (binEntry) {
|
|
108
|
+
const script = path.join(moduleDir, binEntry);
|
|
109
|
+
if (fs.existsSync(script)) {
|
|
110
|
+
return { runtime: process.execPath, script };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
/* not installed via this name */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* For a single target, pkg writes to the exact --output path (with .exe on
|
|
122
|
+
* Windows). For multiple targets it suffixes -<platform>-<arch>.
|
|
123
|
+
*/
|
|
124
|
+
function collectOutputs(outputPath, targets) {
|
|
125
|
+
const dir = path.dirname(outputPath);
|
|
126
|
+
if (!fs.existsSync(dir)) return [];
|
|
127
|
+
const base = path.basename(outputPath);
|
|
128
|
+
const all = fs.readdirSync(dir);
|
|
129
|
+
return all
|
|
130
|
+
.filter(
|
|
131
|
+
(name) =>
|
|
132
|
+
name === base ||
|
|
133
|
+
name.startsWith(base + "-") ||
|
|
134
|
+
name === base + ".exe" ||
|
|
135
|
+
name.startsWith(base + "-"),
|
|
136
|
+
)
|
|
137
|
+
.map((name) => path.join(dir, name))
|
|
138
|
+
.filter((p) => fs.statSync(p).isFile());
|
|
139
|
+
}
|