@crustjs/crust 0.5.6 → 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/README.md CHANGED
@@ -16,7 +16,7 @@ crust build --artifact binary # stage standalone binaries in .crust/
16
16
  crust publish # publish what crust build staged
17
17
  ```
18
18
 
19
- Every build selects its artifact with `--artifact` or `"crust": { "artifact": "package" | "binary" }` in package.json. Runtime packages for the `deno` runtime are experimental: they need deno 2.5.0 or newer to build, and consumers run them with `deno run npm:<package>/<command>` or `deno install -g`, choosing the permissions themselves.
19
+ Every build selects its artifact with `--artifact` or `"crust": { "artifact": "package" | "binary" }` in package.json.
20
20
 
21
21
  The same build is available to release scripts:
22
22
 
package/bin/crust.js CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  // Auto-generated by crust build -- do not edit
3
- import { spawn } from "node:child_process";
4
- import { chmodSync, existsSync } from "node:fs";
5
- import { dirname, resolve } from "node:path";
6
- import process from "node:process";
7
- import { fileURLToPath } from "node:url";
8
-
9
3
  const NAME = "crust";
4
+ if (typeof process.getBuiltinModule !== "function" ||
5
+ (process.platform !== "win32" && typeof process.execve !== "function")) {
6
+ console.error("[" + NAME + "] Requires Node.js >=24 (found " + process.version + ").");
7
+ process.exit(1);
8
+ }
9
+
10
+ // Builtins come from process.getBuiltinModule (cheaper than imports) and never from
11
+ // node:process: importing it touches stdio, which leaves the shared terminal and pipes
12
+ // non-blocking for the binary after execve.
13
+ const { chmodSync, closeSync, existsSync, openSync, readSync } = process.getBuiltinModule("node:fs");
14
+ const { resolve } = process.getBuiltinModule("node:path");
15
+
10
16
  const PLATFORMS = {
11
17
  "linux-x64": {
12
18
  "packagePathSegment": "crust-linux-x64",
@@ -57,10 +63,20 @@ const PLATFORMS = {
57
63
  "binaryFilename": "crust-bun-windows-arm64.exe"
58
64
  }
59
65
  };
60
- const dir = dirname(fileURLToPath(import.meta.url));
66
+ const dir = import.meta.dirname;
61
67
 
62
- // Same check as Bun's own npm installer: glibc reports its version, musl does not.
63
68
  function isMusl() {
69
+ // The first 2 KB of Node's ELF image hold its interpreter path (detect-libc reads the same
70
+ // span), which names its libc without the milliseconds a diagnostic report costs on every start.
71
+ try {
72
+ const fd = openSync("/proc/self/exe", "r");
73
+ const head = Buffer.alloc(2048);
74
+ const interpreter = head.toString("latin1", 0, readSync(fd, head, 0, head.length, 0));
75
+ closeSync(fd);
76
+ if (interpreter.includes("/ld-musl-")) return true;
77
+ if (interpreter.includes("/ld-linux-")) return false;
78
+ } catch {}
79
+ // Same check as Bun's own npm installer: glibc reports its version, musl does not.
64
80
  try {
65
81
  const report = process.report?.getReport();
66
82
  if (report?.header) return report.header.glibcVersionRuntime === undefined;
@@ -98,32 +114,49 @@ if (!binPath) {
98
114
  process.exit(1);
99
115
  }
100
116
 
101
- if (process.platform !== "win32") {
117
+ if (process.platform === "win32") {
118
+ launchWindows(binPath);
119
+ } else {
102
120
  try {
103
121
  chmodSync(binPath, 0o755);
104
122
  } catch {
105
- // Ignore permission adjustment failures and let spawn surface real errors.
123
+ // Ignore permission adjustment failures and let execve surface real errors.
106
124
  }
125
+ // Become the binary: one process, so signals and exit status reach it with no relay.
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.
128
+ process.on("SIGPIPE", () => {});
129
+ process.on("SIGXFSZ", () => {});
130
+ // Early execve versions (e.g. Node 24.0.0) default to an empty environment.
131
+ process.execve(binPath, [binPath, ...process.argv.slice(2)], process.env);
107
132
  }
108
133
 
109
- const child = spawn(binPath, process.argv.slice(2), {
110
- stdio: "inherit",
111
- });
112
-
113
- child.on("error", (error) => {
114
- console.error("[" + NAME + "] Failed to launch binary: " + error.message);
115
- process.exit(1);
116
- });
117
-
118
- child.on("exit", (code, signal) => {
119
- if (signal) {
120
- try {
121
- process.kill(process.pid, signal);
122
- } catch {
123
- process.exit(1);
134
+ // Windows has no execve. Its console delivers Ctrl-C to the binary directly, and
135
+ // child.kill("SIGINT") would hard-terminate it, so SIGINT is never forwarded.
136
+ function launchWindows(binPath) {
137
+ const { spawn } = process.getBuiltinModule("node:child_process");
138
+ const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
139
+ const forward = (signal) => child.kill(signal);
140
+ const ignore = () => {};
141
+ process.on("SIGINT", ignore);
142
+ process.on("SIGTERM", forward);
143
+ process.on("SIGHUP", forward);
144
+ child.on("error", (error) => {
145
+ console.error("[" + NAME + "] Failed to launch binary: " + error.message);
146
+ process.exit(1);
147
+ });
148
+ child.on("exit", (code, signal) => {
149
+ process.off("SIGINT", ignore);
150
+ process.off("SIGTERM", forward);
151
+ process.off("SIGHUP", forward);
152
+ if (signal) {
153
+ try {
154
+ process.kill(process.pid, signal);
155
+ } catch {
156
+ process.exit(1);
157
+ }
158
+ return;
124
159
  }
125
- return;
126
- }
127
-
128
- process.exit(code ?? 0);
129
- });
160
+ process.exit(code ?? 0);
161
+ });
162
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { BuildReport, BuildReport as BuildReport$1 } from "@crustjs/core";
2
- //#region src/distribute.d.ts
2
+ declare const BUN_TARGET_NAMES: readonly ["bun-linux-x64", "bun-linux-arm64", "bun-linux-x64-musl", "bun-linux-arm64-musl", "bun-darwin-x64", "bun-darwin-arm64", "bun-windows-x64", "bun-windows-arm64"];
3
+ type BunTarget = (typeof BUN_TARGET_NAMES)[number];
4
+ declare const NODE_TARGET_NAMES: readonly ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win-x64", "win-arm64"];
5
+ type NodeTarget = (typeof NODE_TARGET_NAMES)[number];
6
+ /** `--target` value that stands for this machine's canonical target. */
7
+ declare const HOST_TARGET = "host";
3
8
  /**
4
9
  * What one build distributes: a runtime `package` (a root-only JavaScript
5
10
  * bundle that needs the runtime installed) or standalone `binary` executables
@@ -31,8 +36,6 @@ type BuildArtifact = {
31
36
  path: string;
32
37
  command: string;
33
38
  };
34
- //#endregion
35
- //#region src/build.d.ts
36
39
  /** Options of the programmatic {@link build}; `crust build` maps its flags onto them. */
37
40
  type BuildOptions = {
38
41
  /** Project root whose package.json `bin` and `crust` are read. Default: `process.cwd()`. */
@@ -47,13 +50,20 @@ type BuildOptions = {
47
50
  /**
48
51
  * Canonical compiler targets, or `"host"` for this machine. Overrides
49
52
  * package.json `crust.targets`; omit both to stage every target of the
50
- * binary compiler. Rejected for runtime packages.
53
+ * binary compiler. Rejected for runtime packages. Known names are completion
54
+ * hints only; any string is checked at build time.
51
55
  */
52
- targets?: readonly string[];
53
- /** Env files inlining `PUBLIC_*` build-time constants, resolved against `cwd`. Rejected for deno. */
56
+ targets?: readonly (BunTarget | NodeTarget | typeof HOST_TARGET | (string & {}))[];
57
+ /** Env files inlining `PUBLIC_*` build-time constants, resolved against `cwd`. */
54
58
  envFiles?: readonly string[];
55
- /** Minify the output. Default: true for bun and node; an explicit `true` is rejected for deno. */
59
+ /** Minify the output. Default: true. */
56
60
  minify?: boolean;
61
+ /**
62
+ * Precompile Bun standalone executables to bytecode for faster startup. Default:
63
+ * true for bun binaries; an explicit `true` is rejected for other runtimes and for
64
+ * runtime packages, whose consumer runtime may not read Bun bytecode.
65
+ */
66
+ bytecode?: boolean;
57
67
  /** Materialize Command Snapshots and run Extension build hooks before compiling. Default: true. */
58
68
  validate?: boolean;
59
69
  /** Receives each progress line `crust build` would print; silent when omitted. Warnings arrive as `"stderr"`. */
@@ -76,10 +86,8 @@ type BuildResult = {
76
86
  * come from `bin`; the runtime, artifact, Bun plugins, extra directories, and
77
87
  * external dependencies from package.json `crust`. Bundling and Command
78
88
  * Snapshots run in bun subprocesses (bun on PATH, or the running Bun
79
- * executable), so this works under Node as well when Bun is installed; Deno
80
- * binaries and Deno runtime packages (experimental, deno 2.5.0 or newer) need
81
- * deno on PATH, and Node binaries a node on PATH that Crust's tsdown can build
82
- * executables with.
89
+ * executable), so this works under Node as well when Bun is installed; Node
90
+ * binaries need a node on PATH that Crust's tsdown can build executables with.
83
91
  *
84
92
  * Throws on any failure. Planning and compiler-selection failures (bad
85
93
  * options or package.json, a missing or hung compiler, an engines mismatch) leave the
@@ -89,5 +97,4 @@ type BuildResult = {
89
97
  * process may leave `.crust.lock`; remove it only when no build is running.
90
98
  */
91
99
  export declare function build(options?: BuildOptions): Promise<BuildResult>;
92
- //#endregion
93
100
  export type { BuildArtifact, BuildOptions, BuildReport, BuildResult };