@crustjs/crust 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/crust.js +1 -0
- package/dist/index.js +69 -21
- package/package.json +9 -9
package/bin/crust.js
CHANGED
|
@@ -124,6 +124,7 @@ if (process.platform === "win32") {
|
|
|
124
124
|
}
|
|
125
125
|
// Become the binary: one process, so signals and exit status reach it with no relay.
|
|
126
126
|
// Node ignores SIGPIPE and SIGXFSZ, and ignored signals survive execve; handled ones reset.
|
|
127
|
+
// Ignores Node inherited (nohup's SIGHUP) were reset at its startup, so none pass on.
|
|
127
128
|
process.on("SIGPIPE", () => {});
|
|
128
129
|
process.on("SIGXFSZ", () => {});
|
|
129
130
|
// Early execve versions (e.g. Node 24.0.0) default to an empty environment.
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { chmod, copyFile, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { chmod, copyFile, mkdtemp, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
@@ -62,7 +62,7 @@ function readIgnoredMask() {
|
|
|
62
62
|
function isErrnoException(error) {
|
|
63
63
|
return error instanceof Error && "code" in error && typeof error.code === "string";
|
|
64
64
|
}
|
|
65
|
-
// ../core/dist/invocation-
|
|
65
|
+
// ../core/dist/invocation-DKSTuFCO.js
|
|
66
66
|
/**
|
|
67
67
|
* A typed error for runtime recipe, Extension, Context, documentation, argv, and validation failures.
|
|
68
68
|
*
|
|
@@ -170,6 +170,9 @@ function readForceColor(overrides) {
|
|
|
170
170
|
function forceColorDisables(forceColor) {
|
|
171
171
|
return forceColor === "0" || forceColor === "false";
|
|
172
172
|
}
|
|
173
|
+
function isDumbTerm(term) {
|
|
174
|
+
return term?.toLowerCase() === "dumb";
|
|
175
|
+
}
|
|
173
176
|
function isTrueColorTerm(term) {
|
|
174
177
|
const lower = term.toLowerCase();
|
|
175
178
|
return lower.includes("24bit") || lower.includes("truecolor") || lower.endsWith("-direct");
|
|
@@ -193,19 +196,20 @@ function detectDepth(colorTerm, term) {
|
|
|
193
196
|
* - `"never"` → `"none"`.
|
|
194
197
|
* - `"always"` → `"truecolor"`.
|
|
195
198
|
* - `"auto"`:
|
|
196
|
-
* 1. `FORCE_COLOR` set → decides unconditionally (overrides TTY
|
|
197
|
-
* `NO_COLOR`,
|
|
199
|
+
* 1. `FORCE_COLOR` set → decides unconditionally (overrides TTY,
|
|
200
|
+
* `NO_COLOR`, and `TERM=dumb`, as in Node's `tty` color detection):
|
|
201
|
+
* `0` / `false` → `"none"`; `1` →
|
|
198
202
|
* `"16"`; `2` → `"256"`; `3` → `"truecolor"`; any other value
|
|
199
203
|
* (including empty) → forced on at the `COLORTERM` / `TERM`
|
|
200
204
|
* detected depth.
|
|
201
|
-
* 2. Not a TTY OR `NO_COLOR` set non-empty
|
|
205
|
+
* 2. Not a TTY OR `NO_COLOR` set non-empty OR `TERM=dumb`
|
|
206
|
+
* (case-insensitive) → `"none"`.
|
|
202
207
|
* 3. `COLORTERM` is `"truecolor"` or `"24bit"` (case-insensitive) →
|
|
203
208
|
* `"truecolor"`.
|
|
204
209
|
* 4. `TERM` ends with `-direct` OR contains `truecolor` / `24bit` →
|
|
205
210
|
* `"truecolor"`.
|
|
206
|
-
* 5. `TERM
|
|
207
|
-
* 6.
|
|
208
|
-
* 7. Any other TTY value → `"16"`.
|
|
211
|
+
* 5. `TERM` contains `256color` → `"256"`.
|
|
212
|
+
* 6. Any other TTY value → `"16"`.
|
|
209
213
|
*
|
|
210
214
|
* Detection follows the ecosystem `FORCE_COLOR` / `NO_COLOR` / `COLORTERM`
|
|
211
215
|
* / `TERM` conventions; no bespoke environment variables are introduced.
|
|
@@ -247,14 +251,15 @@ function resolveColorDepth(mode, overrides) {
|
|
|
247
251
|
if (!readTTY(overrides)) return "none";
|
|
248
252
|
const noColor = read(overrides, "noColor", process.env.NO_COLOR);
|
|
249
253
|
if (noColor !== void 0 && noColor !== "") return "none";
|
|
250
|
-
if (
|
|
254
|
+
if (isDumbTerm(term)) return "none";
|
|
251
255
|
return detectDepth(colorTerm, term);
|
|
252
256
|
}
|
|
253
257
|
/**
|
|
254
258
|
* Resolve whether non-color ANSI modifiers should be emitted.
|
|
255
259
|
*
|
|
256
|
-
* In `"auto"` mode, modifiers are enabled when stdout is a TTY
|
|
257
|
-
* **not** affected by `NO_COLOR` (which only
|
|
260
|
+
* In `"auto"` mode, modifiers are enabled when stdout is a TTY and `TERM`
|
|
261
|
+
* is not `dumb`, but are **not** affected by `NO_COLOR` (which only
|
|
262
|
+
* controls color output).
|
|
258
263
|
* `FORCE_COLOR`, when set, decides unconditionally — it is the all-ANSI
|
|
259
264
|
* switch, while `NO_COLOR` is the colors-only switch.
|
|
260
265
|
*
|
|
@@ -266,7 +271,7 @@ function resolveModifierCapability(mode, overrides) {
|
|
|
266
271
|
if (mode === "never") return false;
|
|
267
272
|
const forceColor = readForceColor(overrides);
|
|
268
273
|
if (forceColor !== void 0) return !forceColorDisables(forceColor);
|
|
269
|
-
return readTTY(overrides);
|
|
274
|
+
return readTTY(overrides) && !isDumbTerm(read(overrides, "term", process.env.TERM));
|
|
270
275
|
}
|
|
271
276
|
/**
|
|
272
277
|
* Apply an ANSI style pair to a string with nesting-safe composition.
|
|
@@ -379,20 +384,35 @@ function collectIncludeDirs(cwd, stageDir, include, artifactNames) {
|
|
|
379
384
|
return includeDirs;
|
|
380
385
|
}
|
|
381
386
|
/**
|
|
387
|
+
* Names a template ships on purpose that `npm pack`/`npm publish` drop from
|
|
388
|
+
* every directory of a package whatever its `files` says, in any letter case.
|
|
389
|
+
* A dropped `.gitignore` or `.npmignore` also acts as ignore rules for its own
|
|
390
|
+
* directory. The rest of npm's ignore list (`.DS_Store`, `*.orig`, …) is junk
|
|
391
|
+
* nobody means to publish, so it stays silently dropped.
|
|
392
|
+
*/
|
|
393
|
+
const NPM_DROPPED_NAME = /^\.(?:gitignore|npmignore|npmrc)$/i;
|
|
394
|
+
/**
|
|
382
395
|
* Walks `dir` the way the dereferencing copy will (through symlinked
|
|
383
|
-
* directories) and rejects any path whose real location leaves the project
|
|
396
|
+
* directories) and rejects any path whose real location leaves the project,
|
|
397
|
+
* and any {@link NPM_DROPPED_NAME} name.
|
|
384
398
|
*/
|
|
385
399
|
function assertResolvesInsideProject(cwd, entry, dir) {
|
|
386
400
|
const realCwd = realpathSync(cwd);
|
|
387
401
|
const seen = /* @__PURE__ */ new Set();
|
|
402
|
+
const npmDropped = [];
|
|
388
403
|
const walk = (path) => {
|
|
389
404
|
const real = realpathSync(path);
|
|
390
405
|
if (!isWithin(realCwd, real)) throw new Error(`package.json crust.include entry ${JSON.stringify(entry)} resolves outside the project root: ${relative(cwd, path)} -> ${real}`);
|
|
406
|
+
if (NPM_DROPPED_NAME.test(basename(path))) npmDropped.push(relative(cwd, path));
|
|
391
407
|
if (seen.has(real) || !statSync(path).isDirectory()) return;
|
|
392
408
|
seen.add(real);
|
|
393
409
|
for (const child of readdirSync(path)) walk(join(path, child));
|
|
394
410
|
};
|
|
395
411
|
walk(dir);
|
|
412
|
+
if (npmDropped.length > 0) {
|
|
413
|
+
const listed = npmDropped.sort().map((path) => `\n ${path}`).join("");
|
|
414
|
+
throw new Error(`package.json crust.include entry ${JSON.stringify(entry)} contains files npm leaves out of published packages:${listed}\n Rename each (e.g. .gitignore -> _gitignore) and restore the name when your CLI copies it; scaffold() from @crustjs/create turns a leading _ on a file name into a dot.`);
|
|
415
|
+
}
|
|
396
416
|
}
|
|
397
417
|
/**
|
|
398
418
|
* Copies one entry's Extension build hook output into the shared artifact
|
|
@@ -553,6 +573,7 @@ function killProcessTree(child) {
|
|
|
553
573
|
* calls are passed through unchanged and own their shell escaping.
|
|
554
574
|
*/
|
|
555
575
|
async function runProcess(command, args = [], options = {}) {
|
|
576
|
+
options.signal?.throwIfAborted();
|
|
556
577
|
const windowsShimCommand = getWindowsShimCommand(command, args, options.shell);
|
|
557
578
|
const collect = (options.stdio ?? "collect") === "collect";
|
|
558
579
|
const collectStdout = collect && options.stdout !== "ignore";
|
|
@@ -595,6 +616,17 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
595
616
|
} catch {}
|
|
596
617
|
}
|
|
597
618
|
}) : () => {};
|
|
619
|
+
let forceKillTimer;
|
|
620
|
+
const onAbort = () => {
|
|
621
|
+
try {
|
|
622
|
+
if (detached || process.platform === "win32") killProcessTree(proc);
|
|
623
|
+
else {
|
|
624
|
+
proc.kill("SIGTERM");
|
|
625
|
+
forceKillTimer = setTimeout(() => proc.kill("SIGKILL"), 5e3);
|
|
626
|
+
}
|
|
627
|
+
} catch {}
|
|
628
|
+
};
|
|
629
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
598
630
|
const { text } = process.getBuiltinModule("node:stream/consumers");
|
|
599
631
|
const { once } = process.getBuiltinModule("node:events");
|
|
600
632
|
try {
|
|
@@ -603,6 +635,7 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
603
635
|
collect ? text(proc.stderr) : "",
|
|
604
636
|
once(proc, "close")
|
|
605
637
|
]), stopped]);
|
|
638
|
+
options.signal?.throwIfAborted();
|
|
606
639
|
return {
|
|
607
640
|
exitCode,
|
|
608
641
|
stdout,
|
|
@@ -610,7 +643,9 @@ async function runProcess(command, args = [], options = {}) {
|
|
|
610
643
|
};
|
|
611
644
|
} finally {
|
|
612
645
|
clearTimeout(timer);
|
|
646
|
+
clearTimeout(forceKillTimer);
|
|
613
647
|
release();
|
|
648
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
614
649
|
}
|
|
615
650
|
}
|
|
616
651
|
function isExecutableFile(path) {
|
|
@@ -1804,6 +1839,12 @@ for (const { specifier, source } of options.plugins) {
|
|
|
1804
1839
|
}
|
|
1805
1840
|
plugins.push(plugin);
|
|
1806
1841
|
}
|
|
1842
|
+
if (options.stripShebang) {
|
|
1843
|
+
// Last, so a project plugin's onLoad for the entry still wins. The emptied line keeps line numbers.
|
|
1844
|
+
plugins.push({ name: "crust-strip-shebang", setup(build) {
|
|
1845
|
+
build.onLoad({ filter: new RegExp(\`^(?:\${options.stripShebang.map(RegExp.escape).join("|")})$\`) }, async ({ path, loader }) => ({ contents: (await Bun.file(path).text()).replace(/^#![^\\n]*/, ""), loader }));
|
|
1846
|
+
} });
|
|
1847
|
+
}
|
|
1807
1848
|
const result = await Bun.build({ ...options.build, define: ${JSON.stringify(CRUST_BUILD_DEFINE)}, plugins, throw: false });
|
|
1808
1849
|
if (!result.success) {
|
|
1809
1850
|
for (const log of result.logs) console.error(log);
|
|
@@ -1819,7 +1860,7 @@ if (!options.build.compile) {
|
|
|
1819
1860
|
}
|
|
1820
1861
|
`;
|
|
1821
1862
|
}
|
|
1822
|
-
async function runBunPluginDriver(build, outfilePath, { cwd, envFiles, bunPlugins }, runner) {
|
|
1863
|
+
async function runBunPluginDriver(build, outfilePath, { cwd, envFiles, bunPlugins }, runner, stripShebang) {
|
|
1823
1864
|
const driverPath = join(cwd, `.crust-build-${randomBytes(6).toString("hex")}.ts`);
|
|
1824
1865
|
await writeFile(driverPath, createBunPluginDriverScript({
|
|
1825
1866
|
plugins: bunPlugins.map((specifier) => ({
|
|
@@ -1827,7 +1868,8 @@ async function runBunPluginDriver(build, outfilePath, { cwd, envFiles, bunPlugin
|
|
|
1827
1868
|
source: resolveBunPluginSource(specifier, cwd)
|
|
1828
1869
|
})),
|
|
1829
1870
|
build,
|
|
1830
|
-
outfile: outfilePath
|
|
1871
|
+
outfile: outfilePath,
|
|
1872
|
+
stripShebang
|
|
1831
1873
|
}));
|
|
1832
1874
|
try {
|
|
1833
1875
|
await runBuildProcess(runner, [...toBunEnvFileArgs(envFiles), driverPath], outfilePath, cwd);
|
|
@@ -1906,14 +1948,15 @@ async function runBuildProcess(runner, args, outfilePath, cwd) {
|
|
|
1906
1948
|
*/
|
|
1907
1949
|
async function execScriptBuild(target, entryPath, outfilePath, settings, runner = resolveBunBuildRunner()) {
|
|
1908
1950
|
const { cwd, minify, envFiles, external } = settings;
|
|
1909
|
-
|
|
1951
|
+
const stripShebang = target === "node" && /^#![^\n]*\bbun\b/.test(await readFile(entryPath, "utf8")) ? [entryPath, await realpath(entryPath)] : void 0;
|
|
1952
|
+
if (settings.bunPlugins.length > 0 || stripShebang !== void 0) await runBunPluginDriver({
|
|
1910
1953
|
entrypoints: [entryPath],
|
|
1911
1954
|
minify,
|
|
1912
1955
|
env: "PUBLIC_*",
|
|
1913
1956
|
target,
|
|
1914
1957
|
format: "esm",
|
|
1915
1958
|
external
|
|
1916
|
-
}, outfilePath, settings, runner);
|
|
1959
|
+
}, outfilePath, settings, runner, stripShebang);
|
|
1917
1960
|
else await runBuildProcess(runner, [
|
|
1918
1961
|
"build",
|
|
1919
1962
|
...toBunEnvFileArgs(envFiles),
|
|
@@ -1930,9 +1973,9 @@ async function execScriptBuild(target, entryPath, outfilePath, settings, runner
|
|
|
1930
1973
|
...minify ? ["--minify"] : [],
|
|
1931
1974
|
entryPath
|
|
1932
1975
|
], outfilePath, cwd);
|
|
1933
|
-
const output = await readFile(outfilePath, "utf8");
|
|
1934
|
-
|
|
1935
|
-
await writeFile(outfilePath,
|
|
1976
|
+
const output = (await readFile(outfilePath, "utf8")).replace(/^#![^\n]*(?:\n|$)/, "");
|
|
1977
|
+
if (target === "node" && output.startsWith("// @bun")) throw new Error(`Build failed for ${outfilePath}: Bun emitted Bun-only code for a Node bundle because the entry reached it starting with "#!/usr/bin/env bun".\n Remove that line from ${entryPath}, or from what a crust.bunPlugins onLoad hook returns for it.`);
|
|
1978
|
+
await writeFile(outfilePath, `#!/usr/bin/env ${target}\n${output}`);
|
|
1936
1979
|
if (process.platform !== "win32") await chmod(outfilePath, 493);
|
|
1937
1980
|
}
|
|
1938
1981
|
// src/launcher.ts
|
|
@@ -2027,6 +2070,7 @@ if (process.platform === "win32") {
|
|
|
2027
2070
|
\t}
|
|
2028
2071
|
\t// Become the binary: one process, so signals and exit status reach it with no relay.
|
|
2029
2072
|
\t// Node ignores SIGPIPE and SIGXFSZ, and ignored signals survive execve; handled ones reset.
|
|
2073
|
+
\t// Ignores Node inherited (nohup's SIGHUP) were reset at its startup, so none pass on.
|
|
2030
2074
|
\tprocess.on("SIGPIPE", () => {});
|
|
2031
2075
|
\tprocess.on("SIGXFSZ", () => {});
|
|
2032
2076
|
\t// Early execve versions (e.g. Node 24.0.0) default to an empty environment.
|
|
@@ -3260,6 +3304,10 @@ async function build(options = {}) {
|
|
|
3260
3304
|
const plan = planBuild(options, cwd);
|
|
3261
3305
|
io.stdout(`${dim("Runtime:")} ${plan.runtime} ${dim(`(${plan.runtimeSource})`)}`);
|
|
3262
3306
|
io.stdout(`${dim("Artifact:")} ${plan.artifact}`);
|
|
3307
|
+
for (const { entryPath } of plan.entries) {
|
|
3308
|
+
const named = /^#![^\n]*\b(bun|node)\b/.exec(readFileSync(entryPath, "utf8"))?.[1];
|
|
3309
|
+
if (named !== void 0 && named !== plan.runtime) io.stderr(`${yellow("Warning:")} ${entryPath} starts with a ${named} shebang but crust.runtime is "${plan.runtime}". The build targets ${plan.runtime}; a linked command (npm link) still starts ${named}.`);
|
|
3310
|
+
}
|
|
3263
3311
|
const compilers = await selectCompilers(plan, io);
|
|
3264
3312
|
rmSync(plan.stageDir, {
|
|
3265
3313
|
recursive: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crustjs/crust",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "CLI to build and publish Crust apps as npm packages or standalone binaries",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "chenxin-yan",
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"crust": "bin/crust.js"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@crustjs/crust-linux-x64": "0.6.
|
|
41
|
-
"@crustjs/crust-linux-arm64": "0.6.
|
|
42
|
-
"@crustjs/crust-linux-x64-musl": "0.6.
|
|
43
|
-
"@crustjs/crust-linux-arm64-musl": "0.6.
|
|
44
|
-
"@crustjs/crust-darwin-x64": "0.6.
|
|
45
|
-
"@crustjs/crust-darwin-arm64": "0.6.
|
|
46
|
-
"@crustjs/crust-windows-x64": "0.6.
|
|
47
|
-
"@crustjs/crust-windows-arm64": "0.6.
|
|
40
|
+
"@crustjs/crust-linux-x64": "0.6.1",
|
|
41
|
+
"@crustjs/crust-linux-arm64": "0.6.1",
|
|
42
|
+
"@crustjs/crust-linux-x64-musl": "0.6.1",
|
|
43
|
+
"@crustjs/crust-linux-arm64-musl": "0.6.1",
|
|
44
|
+
"@crustjs/crust-darwin-x64": "0.6.1",
|
|
45
|
+
"@crustjs/crust-darwin-arm64": "0.6.1",
|
|
46
|
+
"@crustjs/crust-windows-x64": "0.6.1",
|
|
47
|
+
"@crustjs/crust-windows-arm64": "0.6.1",
|
|
48
48
|
"tsdown": "0.23.4",
|
|
49
49
|
"@tsdown/exe": "0.23.4"
|
|
50
50
|
},
|