@crustjs/crust 0.0.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 ADDED
@@ -0,0 +1,131 @@
1
+ # @crustjs/crust
2
+
3
+ The all-in-one package for the [Crust](https://crust.cyanlabs.co) CLI framework.
4
+
5
+ Re-exports everything from `@crustjs/core` and `@crustjs/plugins`, plus provides CLI tooling for building Crust-powered CLIs.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add @crustjs/crust
11
+ ```
12
+
13
+ ## Framework API
14
+
15
+ The `@crustjs/crust` package gives you the full framework API in a single import:
16
+
17
+ ```ts
18
+ import {
19
+ defineCommand,
20
+ runMain,
21
+ helpPlugin,
22
+ versionPlugin,
23
+ } from "@crustjs/crust";
24
+
25
+ const main = defineCommand({
26
+ meta: { name: "my-cli", description: "My CLI tool" },
27
+ run() {
28
+ console.log("Hello!");
29
+ },
30
+ });
31
+
32
+ runMain(main, {
33
+ plugins: [versionPlugin("1.0.0"), helpPlugin()],
34
+ });
35
+ ```
36
+
37
+ > For granular control, you can install `@crustjs/core` and `@crustjs/plugins` separately.
38
+
39
+ ## CLI Commands
40
+
41
+ When installed, the `crust` binary provides tooling for your project:
42
+
43
+ | Command | Description |
44
+ | ------------- | ------------------------------------------------ |
45
+ | `crust build` | Compile your CLI to standalone Bun executable(s) |
46
+
47
+ ### `crust build`
48
+
49
+ Compiles your CLI entry file to standalone Bun executables using `bun build --compile`.
50
+
51
+ **By default, builds for all 5 supported platforms** and generates a JS resolver script that detects the host platform at runtime and runs the correct binary. This makes it easy to distribute your CLI as a single npm package that works everywhere.
52
+
53
+ ```sh
54
+ crust build # All platforms + JS resolver (default)
55
+ crust build --entry src/main.ts # Custom entry point
56
+ crust build --name my-tool # Set base binary name
57
+ crust build --no-minify # Disable minification
58
+ ```
59
+
60
+ To build for specific platform(s) only, use `--target`:
61
+
62
+ ```sh
63
+ crust build --target linux-x64 # Single platform
64
+ crust build --target linux-x64 --target darwin-arm64 # Multiple platforms
65
+ crust build --target linux-x64 --outfile ./my-cli # Custom output (single target only)
66
+ ```
67
+
68
+ #### Supported Targets
69
+
70
+ | Alias | Bun Target | Platform |
71
+ | -------------- | -------------------------- | ------------------- |
72
+ | `linux-x64` | `bun-linux-x64-baseline` | Linux x86_64 |
73
+ | `linux-arm64` | `bun-linux-arm64` | Linux ARM64 |
74
+ | `darwin-x64` | `bun-darwin-x64` | macOS Intel |
75
+ | `darwin-arm64` | `bun-darwin-arm64` | macOS Apple Silicon |
76
+ | `windows-x64` | `bun-windows-x64-baseline` | Windows x86_64 |
77
+
78
+ #### Flags
79
+
80
+ | Flag | Alias | Type | Default | Description |
81
+ | ----------- | ----- | --------- | ------------------- | -------------------------------------------- |
82
+ | `--entry` | `-e` | `String` | `src/cli.ts` | Entry file path |
83
+ | `--outfile` | `-o` | `String` | — | Output file path (single-target builds only) |
84
+ | `--name` | `-n` | `String` | package.json `name` | Base binary name |
85
+ | `--minify` | — | `Boolean` | `true` | Minify the output |
86
+ | `--target` | `-t` | `String` | _(all platforms)_ | Target platform(s); repeatable |
87
+
88
+ #### Output
89
+
90
+ **All-platform build** (default, no `--target`):
91
+
92
+ ```
93
+ dist/
94
+ my-cli.js # JS resolver (entry point for npm bin)
95
+ my-cli-bun-linux-x64-baseline # Linux x64 binary
96
+ my-cli-bun-linux-arm64 # Linux ARM64 binary
97
+ my-cli-bun-darwin-x64 # macOS Intel binary
98
+ my-cli-bun-darwin-arm64 # macOS Apple Silicon binary
99
+ my-cli-bun-windows-x64-baseline.exe # Windows x64 binary
100
+ ```
101
+
102
+ **Single-target build** (`--target <alias>`):
103
+
104
+ ```
105
+ dist/
106
+ my-cli # Single binary (no resolver)
107
+ ```
108
+
109
+ #### Distributing via npm
110
+
111
+ After building for all platforms, configure your `package.json` to use the JS resolver as the bin entry:
112
+
113
+ ```json
114
+ {
115
+ "name": "my-cli",
116
+ "bin": {
117
+ "my-cli": "dist/my-cli.js"
118
+ },
119
+ "files": ["dist"]
120
+ }
121
+ ```
122
+
123
+ The resolver uses `#!/usr/bin/env node` for maximum compatibility when installed globally via npm (works even when Bun is not installed on the end user's machine).
124
+
125
+ ## Documentation
126
+
127
+ See the full docs at [crust.cyanlabs.co](https://crust.cyanlabs.co).
128
+
129
+ ## License
130
+
131
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { Command } from "@crustjs/core";
2
+ /**
3
+ * The root `crust` CLI command.
4
+ *
5
+ * Built entirely with `@crustjs/core`.
6
+ * When invoked without a subcommand, displays help listing available commands.
7
+ *
8
+ * Subcommands:
9
+ * - `crust build` - Compile your CLI to a standalone Bun executable
10
+ */
11
+ declare const crustCommand: Command;
12
+ export { crustCommand };
package/dist/cli.js ADDED
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/cli.ts
5
+ import { defineCommand as defineCommand2, runMain } from "@crustjs/core";
6
+ import {
7
+ autoCompletePlugin,
8
+ helpPlugin,
9
+ versionPlugin
10
+ } from "@crustjs/plugins";
11
+ // package.json
12
+ var package_default = {
13
+ name: "@crustjs/crust",
14
+ version: "0.0.1",
15
+ description: "A Bun-native, TypeScript-first CLI framework with a composable package ecosystem.",
16
+ type: "module",
17
+ license: "MIT",
18
+ author: "chenxin-yan",
19
+ repository: {
20
+ type: "git",
21
+ url: "git+https://github.com/chenxin-yan/crust.git",
22
+ directory: "packages/crust"
23
+ },
24
+ homepage: "https://crust.cyanlabs.co",
25
+ bugs: {
26
+ url: "https://github.com/chenxin-yan/crust/issues"
27
+ },
28
+ keywords: [
29
+ "cli",
30
+ "command",
31
+ "framework",
32
+ "parser",
33
+ "bun",
34
+ "typescript",
35
+ "plugin"
36
+ ],
37
+ files: [
38
+ "dist"
39
+ ],
40
+ exports: {
41
+ ".": {
42
+ import: "./dist/index.js",
43
+ types: "./dist/index.d.ts"
44
+ }
45
+ },
46
+ publishConfig: {
47
+ access: "public"
48
+ },
49
+ bin: {
50
+ crust: "dist/cli.js"
51
+ },
52
+ scripts: {
53
+ build: "bunup",
54
+ dev: "bun ./src/cli.ts",
55
+ "check:types": "tsc --noEmit",
56
+ test: "bun test"
57
+ },
58
+ dependencies: {
59
+ "@crustjs/core": "workspace:*",
60
+ "@crustjs/plugins": "workspace:*"
61
+ },
62
+ devDependencies: {
63
+ "@crustjs/config": "workspace:*",
64
+ bunup: "catalog:"
65
+ },
66
+ peerDependencies: {
67
+ typescript: "catalog:"
68
+ }
69
+ };
70
+
71
+ // src/commands/build.ts
72
+ import { existsSync, readFileSync, writeFileSync } from "fs";
73
+ import { basename, join, resolve } from "path";
74
+ import { defineCommand } from "@crustjs/core";
75
+ var SUPPORTED_TARGETS = [
76
+ "bun-linux-x64-baseline",
77
+ "bun-linux-arm64",
78
+ "bun-darwin-x64",
79
+ "bun-darwin-arm64",
80
+ "bun-windows-x64-baseline"
81
+ ];
82
+ var TARGET_ALIASES = {
83
+ "linux-x64": "bun-linux-x64-baseline",
84
+ "linux-arm64": "bun-linux-arm64",
85
+ "darwin-x64": "bun-darwin-x64",
86
+ "darwin-arm64": "bun-darwin-arm64",
87
+ "windows-x64": "bun-windows-x64-baseline"
88
+ };
89
+ var TARGET_PLATFORM_MAP = {
90
+ "bun-linux-x64-baseline": "linux-x64",
91
+ "bun-linux-arm64": "linux-arm64",
92
+ "bun-darwin-x64": "darwin-x64",
93
+ "bun-darwin-arm64": "darwin-arm64",
94
+ "bun-windows-x64-baseline": "win32-x64"
95
+ };
96
+ function resolveTarget(input) {
97
+ if (SUPPORTED_TARGETS.includes(input)) {
98
+ return input;
99
+ }
100
+ const aliased = TARGET_ALIASES[input];
101
+ if (aliased) {
102
+ return aliased;
103
+ }
104
+ const validTargets = [
105
+ ...Object.keys(TARGET_ALIASES),
106
+ ...SUPPORTED_TARGETS
107
+ ].join(", ");
108
+ throw new Error(`Unknown target "${input}"
109
+ Valid targets: ${validTargets}`);
110
+ }
111
+ function resolveBaseName(name, entry, cwd) {
112
+ if (name) {
113
+ return name;
114
+ }
115
+ const pkgPath = join(cwd, "package.json");
116
+ if (existsSync(pkgPath)) {
117
+ try {
118
+ const pkgJson = JSON.parse(readFileSync(pkgPath, "utf-8"));
119
+ if (pkgJson?.name && typeof pkgJson.name === "string") {
120
+ return pkgJson.name.replace(/^@[^/]+\//, "");
121
+ }
122
+ } catch {}
123
+ }
124
+ return basename(entry).replace(/\.[^.]+$/, "");
125
+ }
126
+ function resolveOutfile(outfile, name, entry, cwd) {
127
+ if (outfile) {
128
+ return resolve(cwd, outfile);
129
+ }
130
+ const baseName = resolveBaseName(name, entry, cwd);
131
+ return resolve(cwd, "dist", baseName);
132
+ }
133
+ function resolveTargetOutfile(baseName, target, cwd) {
134
+ const isWindows = target.startsWith("bun-windows");
135
+ const ext = isWindows ? ".exe" : "";
136
+ return resolve(cwd, "dist", `${baseName}-${target}${ext}`);
137
+ }
138
+ function getBinaryFilename(baseName, target) {
139
+ const isWindows = target.startsWith("bun-windows");
140
+ const ext = isWindows ? ".exe" : "";
141
+ return `${baseName}-${target}${ext}`;
142
+ }
143
+ function generateResolver(baseName, targets) {
144
+ const entries = [];
145
+ for (const target of targets) {
146
+ const platformKey = TARGET_PLATFORM_MAP[target];
147
+ const filename = getBinaryFilename(baseName, target);
148
+ entries.push(` "${platformKey}": "${filename}"`);
149
+ }
150
+ const mapBody = entries.join(`,
151
+ `);
152
+ return `#!/usr/bin/env node
153
+ "use strict";
154
+
155
+ const { execFileSync } = require("node:child_process");
156
+ const { chmodSync, statSync } = require("node:fs");
157
+ const path = require("node:path");
158
+
159
+ const PLATFORM_ARCH = \`\${process.platform}-\${process.arch}\`;
160
+
161
+ const TARGET_MAP = {
162
+ ${mapBody}
163
+ };
164
+
165
+ const binaryName = TARGET_MAP[PLATFORM_ARCH];
166
+
167
+ if (!binaryName) {
168
+ console.error(
169
+ \`[${baseName}] Unsupported platform: \${PLATFORM_ARCH}. \` +
170
+ "Please open an issue with your OS/CPU details."
171
+ );
172
+ process.exit(1);
173
+ }
174
+
175
+ const binPath = path.join(__dirname, binaryName);
176
+
177
+ try {
178
+ statSync(binPath);
179
+ } catch {
180
+ console.error(\`[${baseName}] Prebuilt binary not found: \${binPath}\`);
181
+ console.error(\`[${baseName}] Expected binary for \${PLATFORM_ARCH}: \${binaryName}\`);
182
+ console.error(\`[${baseName}] Try reinstalling the package.\`);
183
+ process.exit(1);
184
+ }
185
+
186
+ // Ensure binary is executable on Unix
187
+ if (process.platform !== "win32") {
188
+ try {
189
+ const stats = statSync(binPath);
190
+ if ((stats.mode & 0o111) === 0) {
191
+ chmodSync(binPath, stats.mode | 0o111);
192
+ }
193
+ } catch {
194
+ try {
195
+ chmodSync(binPath, 0o755);
196
+ } catch {
197
+ // If chmod fails, continue and let execFileSync report the error
198
+ }
199
+ }
200
+ }
201
+
202
+ try {
203
+ execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
204
+ } catch (error) {
205
+ if (error && typeof error === "object" && "status" in error && typeof error.status === "number") {
206
+ process.exit(error.status);
207
+ }
208
+ process.exit(1);
209
+ }
210
+ `;
211
+ }
212
+ function writeResolver(resolverPath, baseName, targets) {
213
+ const content = generateResolver(baseName, targets);
214
+ writeFileSync(resolverPath, content, { mode: 493 });
215
+ }
216
+ function buildBunArgs(entryPath, outfilePath, minify, target) {
217
+ const args = [
218
+ "build",
219
+ "--compile",
220
+ entryPath,
221
+ "--outfile",
222
+ outfilePath
223
+ ];
224
+ if (target) {
225
+ args.push("--target", target);
226
+ }
227
+ if (minify) {
228
+ args.push("--minify");
229
+ }
230
+ return args;
231
+ }
232
+ async function execBuild(args, cwd, outfilePath) {
233
+ const proc = Bun.spawn(["bun", ...args], {
234
+ cwd,
235
+ stdout: "pipe",
236
+ stderr: "pipe"
237
+ });
238
+ const exitCode = await proc.exited;
239
+ if (exitCode !== 0) {
240
+ const stderr = await new Response(proc.stderr).text();
241
+ throw new Error(`Build failed for ${outfilePath} (exit code ${exitCode})${stderr ? `:
242
+ ${stderr.trim()}` : ""}`);
243
+ }
244
+ }
245
+ var buildCommand = defineCommand({
246
+ meta: {
247
+ name: "build",
248
+ description: "Compile your CLI to a standalone executable"
249
+ },
250
+ flags: {
251
+ entry: {
252
+ type: String,
253
+ description: "Entry file path",
254
+ default: "src/cli.ts",
255
+ alias: "e"
256
+ },
257
+ outfile: {
258
+ type: String,
259
+ description: "Output file path (single-target builds only)",
260
+ alias: "o"
261
+ },
262
+ name: {
263
+ type: String,
264
+ description: "Binary name (defaults to package.json name or entry filename)",
265
+ alias: "n"
266
+ },
267
+ minify: {
268
+ type: Boolean,
269
+ description: "Minify the output",
270
+ default: true
271
+ },
272
+ target: {
273
+ type: String,
274
+ multiple: true,
275
+ description: "Target platform(s) to compile for (e.g. linux-x64, darwin-arm64). Omit to build all.",
276
+ alias: "t"
277
+ }
278
+ },
279
+ async run({ flags }) {
280
+ const cwd = process.cwd();
281
+ const entryPath = resolve(cwd, flags.entry);
282
+ if (!existsSync(entryPath)) {
283
+ throw new Error(`Entry file not found: ${entryPath}
284
+ Specify a valid entry file with --entry <path>`);
285
+ }
286
+ const targets = resolveTargets(flags.target);
287
+ if (flags.outfile && targets.length > 1) {
288
+ throw new Error(`--outfile cannot be used when building for multiple targets.
289
+ Use --name to set the base binary name instead.`);
290
+ }
291
+ if (targets.length === 1) {
292
+ const outfilePath = resolveOutfile(flags.outfile, flags.name, entryPath, cwd);
293
+ const args = buildBunArgs(entryPath, outfilePath, flags.minify, targets[0]);
294
+ console.log(`Building ${entryPath} \u2192 ${outfilePath}...`);
295
+ await execBuild(args, cwd, outfilePath);
296
+ console.log(`Built successfully: ${outfilePath}`);
297
+ } else {
298
+ const baseName = resolveBaseName(flags.name, entryPath, cwd);
299
+ console.log(`Building ${entryPath} for ${targets.length} target(s)...`);
300
+ const results = [];
301
+ for (const target of targets) {
302
+ const targetOutfile = resolveTargetOutfile(baseName, target, cwd);
303
+ const args = buildBunArgs(entryPath, targetOutfile, flags.minify, target);
304
+ console.log(` \u2192 ${target}: ${targetOutfile}`);
305
+ await execBuild(args, cwd, targetOutfile);
306
+ results.push(targetOutfile);
307
+ }
308
+ const resolverPath = resolve(cwd, "dist", `${baseName}.js`);
309
+ writeResolver(resolverPath, baseName, targets);
310
+ console.log(`
311
+ Built ${results.length} target(s) successfully:`);
312
+ for (const r of results) {
313
+ console.log(` ${r}`);
314
+ }
315
+ console.log(`
316
+ Resolver: ${resolverPath}`);
317
+ }
318
+ }
319
+ });
320
+ function resolveTargets(targetFlags) {
321
+ if (!targetFlags || targetFlags.length === 0) {
322
+ return [...SUPPORTED_TARGETS];
323
+ }
324
+ return targetFlags.map(resolveTarget);
325
+ }
326
+
327
+ // src/cli.ts
328
+ var crustCommand = defineCommand2({
329
+ meta: {
330
+ name: package_default.name,
331
+ description: package_default.description
332
+ },
333
+ subCommands: {
334
+ build: buildCommand
335
+ }
336
+ });
337
+ runMain(crustCommand, {
338
+ plugins: [
339
+ versionPlugin(package_default.version),
340
+ autoCompletePlugin({ mode: "help" }),
341
+ helpPlugin()
342
+ ]
343
+ });
344
+ export {
345
+ crustCommand
346
+ };
@@ -0,0 +1,2 @@
1
+ export * from "@crustjs/core";
2
+ export * from "@crustjs/plugins";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ // src/index.ts
3
+ export * from "@crustjs/core";
4
+ export * from "@crustjs/plugins";
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@crustjs/crust",
3
+ "version": "0.0.1",
4
+ "description": "A Bun-native, TypeScript-first CLI framework with a composable package ecosystem.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "chenxin-yan",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/chenxin-yan/crust.git",
11
+ "directory": "packages/crust"
12
+ },
13
+ "homepage": "https://crust.cyanlabs.co",
14
+ "bugs": {
15
+ "url": "https://github.com/chenxin-yan/crust/issues"
16
+ },
17
+ "keywords": [
18
+ "cli",
19
+ "command",
20
+ "framework",
21
+ "parser",
22
+ "bun",
23
+ "typescript",
24
+ "plugin"
25
+ ],
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/index.js",
32
+ "types": "./dist/index.d.ts"
33
+ }
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "bin": {
39
+ "crust": "dist/cli.js"
40
+ },
41
+ "scripts": {
42
+ "build": "bunup",
43
+ "dev": "bun ./src/cli.ts",
44
+ "check:types": "tsc --noEmit",
45
+ "test": "bun test"
46
+ },
47
+ "dependencies": {
48
+ "@crustjs/core": "workspace:*",
49
+ "@crustjs/plugins": "workspace:*"
50
+ },
51
+ "devDependencies": {
52
+ "@crustjs/config": "workspace:*",
53
+ "bunup": "catalog:"
54
+ },
55
+ "peerDependencies": {
56
+ "typescript": "catalog:"
57
+ }
58
+ }