create-disposabl-app 0.0.3 → 0.1.0

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/dist/index.js CHANGED
@@ -1,108 +1,513 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // cli/index.ts
4
- import { parseArgs } from "node:util";
5
-
6
- // cli/create-app.ts
3
+ // index.ts
7
4
  import { spawn, spawnSync } from "node:child_process";
5
+ import { existsSync as existsSync2 } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { Command, CommanderError } from "commander";
9
+ import * as p2 from "@clack/prompts";
10
+ // package.json
11
+ var version = "0.1.0";
12
+
13
+ // options.ts
14
+ var platforms = [
15
+ { id: "web", label: "Web", group: "Required", status: "required" },
16
+ { id: "android", label: "Android", group: "Mobile", status: "available" },
17
+ { id: "ios", label: "iOS", group: "Mobile", status: "available" },
18
+ { id: "windows", label: "Windows", group: "Desktop", status: "available" },
19
+ { id: "linux", label: "Linux", group: "Desktop", status: "available" },
20
+ { id: "macos", label: "macOS", group: "Desktop", status: "available" },
21
+ {
22
+ id: "mcp",
23
+ label: "MCP",
24
+ group: "MCP & plugins",
25
+ status: "available",
26
+ description: "Tools for AI assistants"
27
+ },
28
+ {
29
+ id: "openai-plugin",
30
+ label: "OpenAI Plugin",
31
+ description: "Tools and skills for OpenAI",
32
+ group: "MCP & plugins",
33
+ status: "available"
34
+ },
35
+ {
36
+ id: "claude-code-plugin",
37
+ label: "Claude Code Plugin",
38
+ description: "Tools and skills for Claude Code",
39
+ group: "MCP & plugins",
40
+ status: "available"
41
+ },
42
+ { id: "api", label: "API", group: "Coming soon", status: "disabled" },
43
+ { id: "cli", label: "CLI", group: "Coming soon", status: "disabled" },
44
+ {
45
+ id: "python-sdk",
46
+ label: "Python SDK",
47
+ group: "Coming soon",
48
+ status: "disabled"
49
+ },
50
+ {
51
+ id: "typescript-sdk",
52
+ label: "TypeScript SDK",
53
+ group: "Coming soon",
54
+ status: "disabled"
55
+ }
56
+ ];
57
+ var platformDependencies = {
58
+ "openai-plugin": ["mcp"],
59
+ "claude-code-plugin": ["mcp"]
60
+ };
61
+ var features = [
62
+ { id: "docs", label: "Docs", hint: "apps/docs" },
63
+ { id: "blog", label: "Blog", hint: "apps/blog" },
64
+ { id: "changelog", label: "Changelog", hint: "apps/changelog" },
65
+ {
66
+ id: "authentication",
67
+ label: "Authentication",
68
+ hint: "Record selection only"
69
+ }
70
+ ];
71
+ var parseList = (input) => input.split(",").map((value) => value.trim());
72
+ function selectPlatforms(input = "web") {
73
+ const requested = parseList(input);
74
+ for (const id of requested) {
75
+ const option = platforms.find((platform) => platform.id === id);
76
+ if (!option)
77
+ throw new Error("Unknown platform " + JSON.stringify(id) + ". Use --list-options to see valid IDs.");
78
+ if (option.status === "disabled")
79
+ throw new Error(option.label + " is not available yet and cannot be selected.");
80
+ }
81
+ for (const [plugin, dependencies] of Object.entries(platformDependencies)) {
82
+ if (requested.includes(plugin))
83
+ requested.push(...dependencies);
84
+ }
85
+ return platforms.filter((option) => option.id === "web" || requested.includes(option.id)).map((option) => option.id);
86
+ }
87
+ function selectFeatures(input = "none") {
88
+ if (input === "none")
89
+ return [];
90
+ const requested = parseList(input);
91
+ for (const id of requested) {
92
+ if (!features.some((feature) => feature.id === id)) {
93
+ throw new Error("Unknown feature " + JSON.stringify(id) + ". Use --features none or --list-options to see valid IDs.");
94
+ }
95
+ }
96
+ return features.filter((option) => requested.includes(option.id)).map((option) => option.id);
97
+ }
98
+ function selectedFolders(config) {
99
+ const result = ["apps/web"];
100
+ if (config.platforms.some((platform) => platform === "android" || platform === "ios"))
101
+ result.push("apps/mobile");
102
+ if (config.platforms.some((platform) => ["windows", "linux", "macos"].includes(platform)))
103
+ result.push("apps/desktop");
104
+ for (const feature of config.features) {
105
+ if (feature !== "authentication")
106
+ result.push("apps/" + feature);
107
+ }
108
+ return result;
109
+ }
110
+ function validateName(name) {
111
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(name) || name.length > 214 || ["node_modules", "favicon.ico"].includes(name)) {
112
+ return "Use a lowercase project name with letters, numbers, dots, hyphens, or underscores.";
113
+ }
114
+ }
115
+
116
+ // platform-prompt.ts
117
+ import { MultiSelectPrompt } from "@clack/core";
118
+ import * as p from "@clack/prompts";
119
+ import color from "picocolors";
120
+ function createPlatformPrompt(streams = {}) {
121
+ return new MultiSelectPrompt({
122
+ ...streams,
123
+ options: platforms.map((option) => ({
124
+ value: option.id,
125
+ label: option.label,
126
+ disabled: option.status !== "available",
127
+ description: "description" in option ? option.description : ""
128
+ })),
129
+ required: false,
130
+ render() {
131
+ const explicit = this.value ?? [];
132
+ const selected = selectPlatforms(explicit.length ? explicit.join(",") : "web");
133
+ const title = `${p.symbol(this.state)} Choose platforms`;
134
+ const bar = color.dim(p.S_BAR);
135
+ if (this.state === "submit") {
136
+ const labels = this.options.filter((option) => selected.includes(option.value)).map((option) => option.label.trim());
137
+ return `${title}
138
+ ${bar} ${color.dim(labels.join(", ") || "Web only")}`;
139
+ }
140
+ if (this.state === "cancel")
141
+ return `${title}
142
+ ${bar} ${color.dim("Cancelled")}`;
143
+ const style = (option, active) => {
144
+ const checked = selected.includes(option.value);
145
+ const hint = option.value === "web" ? "Required" : option.disabled ? "Unavailable" : option.description + (option.value.endsWith("-plugin") ? " · Requires MCP" : "");
146
+ const checkbox = checked ? p.S_CHECKBOX_SELECTED : active ? p.S_CHECKBOX_ACTIVE : p.S_CHECKBOX_INACTIVE;
147
+ const label = `${checkbox} ${option.label}`;
148
+ return (option.value === "web" ? color.green(label) : option.disabled ? color.dim(label) : active ? color.cyan(label) : label) + (hint ? color.dim(` (${hint})`) : "");
149
+ };
150
+ const unavailable = this.options.filter((option) => option.disabled && option.value !== "web");
151
+ const rows = p.limitOptions({
152
+ options: this.options.filter((option) => !unavailable.includes(option)),
153
+ cursor: this.cursor,
154
+ output: streams.output,
155
+ columnPadding: 3,
156
+ rowPadding: 6 + unavailable.length,
157
+ style
158
+ });
159
+ return [
160
+ title,
161
+ ...rows.map((row) => `${bar} ${row}`),
162
+ ...unavailable.map((option) => `${bar} ${style(option, false)}`),
163
+ `${bar} ${color.dim("↑/↓ move · Space select · Enter confirm")}`,
164
+ color.dim(p.S_BAR_END)
165
+ ].join(`
166
+ `);
167
+ }
168
+ });
169
+ }
170
+
171
+ // project-setup.ts
172
+ import { clearScreenDown, cursorTo, moveCursor } from "node:readline";
173
+ import color2 from "picocolors";
174
+ import { performance } from "node:perf_hooks";
175
+ function setupTable(name, config, status, columns = 80) {
176
+ const installation = status.installation + (status.elapsedSeconds === undefined ? "" : ` (${status.elapsedSeconds.toFixed(2)}s)`);
177
+ const rows = [
178
+ ["App", name],
179
+ ["Disposabl Version", config.version],
180
+ [
181
+ "Platforms",
182
+ config.platforms.map((id) => platforms.find((p) => p.id === id).label).join(", ")
183
+ ],
184
+ [
185
+ "Features",
186
+ config.features.map((id) => features.find((f) => f.id === id).label).join(", ") || "None"
187
+ ],
188
+ ["Installation", installation]
189
+ ];
190
+ const labelWidth = Math.max(...rows.map(([label]) => label.length));
191
+ const padding = labelWidth + 7;
192
+ const contentWidth = Math.max(46, ...rows.map(([, value]) => value.length + padding));
193
+ const width = Math.max(24, Math.min(columns - 1, 80, contentWidth));
194
+ const valueWidth = width - padding;
195
+ const border = color2.dim("─".repeat(width - 2));
196
+ const result = [color2.bold("App Setup"), `┌${border}┐`];
197
+ for (const [label, value] of rows) {
198
+ let remaining = value;
199
+ let first = true;
200
+ do {
201
+ let end = valueWidth;
202
+ if (remaining.length > end) {
203
+ const space = remaining.lastIndexOf(" ", end);
204
+ if (space > 0)
205
+ end = space;
206
+ }
207
+ const part = remaining.slice(0, end);
208
+ remaining = remaining.slice(end).trimStart();
209
+ const cell = part.padEnd(valueWidth);
210
+ const isStatus = label === "Installation";
211
+ const styled = isStatus && status.installation === "Complete" ? color2.green(cell) : isStatus && status.installation === "Failed" ? color2.red(cell) : label === "App" ? color2.bold(cell) : cell;
212
+ result.push(`│ ${(first ? label : "").padEnd(labelWidth)} │ ${styled} │`);
213
+ first = false;
214
+ } while (remaining);
215
+ }
216
+ result.push(`└${border}┘`);
217
+ return result.join(`
218
+ `);
219
+ }
220
+ function projectSetup(name, config) {
221
+ const live = Boolean(process.stdout.isTTY);
222
+ const status = { installation: "Pending" };
223
+ let lines = 0;
224
+ let started;
225
+ let timer;
226
+ function render() {
227
+ if (live && lines) {
228
+ moveCursor(process.stdout, 0, -lines);
229
+ cursorTo(process.stdout, 0);
230
+ clearScreenDown(process.stdout);
231
+ }
232
+ const frame = setupTable(name, config, status, process.stdout.columns);
233
+ process.stdout.write(frame + `
234
+ `);
235
+ lines = frame.split(`
236
+ `).length;
237
+ }
238
+ if (live)
239
+ render();
240
+ return {
241
+ live,
242
+ installing() {
243
+ started = performance.now();
244
+ status.installation = "Installing";
245
+ const tick = () => {
246
+ status.elapsedSeconds = (performance.now() - started) / 1000;
247
+ if (live)
248
+ render();
249
+ };
250
+ tick();
251
+ if (live)
252
+ timer = setInterval(tick, 100);
253
+ },
254
+ finish(next) {
255
+ clearInterval(timer);
256
+ Object.assign(status, next);
257
+ if (started !== undefined)
258
+ status.elapsedSeconds = (performance.now() - started) / 1000;
259
+ render();
260
+ }
261
+ };
262
+ }
263
+
264
+ // scaffold.ts
8
265
  import {
9
266
  cp,
10
267
  mkdir,
11
- readdir,
12
268
  readFile,
269
+ readdir,
13
270
  rename,
271
+ rm,
14
272
  writeFile
15
273
  } from "node:fs/promises";
16
- import { resolve } from "node:path";
17
- import { fileURLToPath } from "node:url";
18
- async function createApp(name, { cwd = process.cwd(), skipInstall = false } = {}) {
19
- if (!name || !/^[a-z0-9][a-z0-9._-]*$/.test(name) || name.length > 214) {
20
- throw new Error("Choose a folder name using lowercase letters, numbers, dots, underscores, or hyphens.");
21
- }
22
- if (!skipInstall && spawnSync("bun", ["--version"]).status !== 0) {
23
- throw new Error("Install Bun from https://bun.com before creating an app.");
24
- }
25
- const dir = resolve(cwd, name);
26
- await mkdir(dir);
27
- console.log(`Creating ${name}...`);
28
- const template = fileURLToPath(new URL("./templates/default", import.meta.url));
29
- await Promise.all((await readdir(template)).map((file) => cp(resolve(template, file), resolve(dir, file), {
30
- recursive: true,
31
- force: false,
32
- errorOnExist: true
33
- })));
34
- await rename(resolve(dir, "gitignore"), resolve(dir, ".gitignore"));
35
- const manifestPath = resolve(dir, "package.json");
36
- const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
37
- manifest.name = name;
38
- await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
39
- `);
40
- if (!skipInstall) {
41
- await new Promise((resolveInstall, reject) => {
42
- const install = spawn("bun", ["install"], {
43
- cwd: dir,
44
- stdio: "inherit",
45
- env: { ...process.env, NODE_ENV: "development" }
46
- });
47
- install.once("error", reject);
48
- install.once("close", (code) => {
49
- if (code === 0)
50
- resolveInstall();
51
- else {
52
- reject(new Error(`Dependencies could not be installed. Run bun install inside ${name} to retry.`));
53
- }
54
- });
55
- });
274
+ import { existsSync } from "node:fs";
275
+ import { join } from "node:path";
276
+ async function createProject(template, destination, name, config) {
277
+ const folders = selectedFolders(config);
278
+ for (const folder of folders) {
279
+ if (!existsSync(join(template, folder, "package.json"))) {
280
+ throw new Error("The bundled starter is missing " + folder + "/package.json. Rebuild the CLI.");
281
+ }
56
282
  }
57
- console.log(`
58
- Your app is ready.
59
-
60
- cd ${name}
61
- ${skipInstall ? ` bun install
62
- ` : ""} bun run dev
283
+ await mkdir(destination);
284
+ try {
285
+ for (const entry of await readdir(template)) {
286
+ await cp(join(template, entry), join(destination, entry), {
287
+ recursive: true,
288
+ force: false,
289
+ errorOnExist: true
290
+ });
291
+ }
292
+ for (const entry of await readdir(join(destination, "apps"))) {
293
+ if (!folders.includes("apps/" + entry))
294
+ await rm(join(destination, "apps", entry), {
295
+ recursive: true,
296
+ force: true
297
+ });
298
+ }
299
+ if (existsSync(join(destination, "gitignore"))) {
300
+ await rename(join(destination, "gitignore"), join(destination, ".gitignore"));
301
+ }
302
+ const manifestPath = join(destination, "package.json");
303
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
304
+ manifest.name = name;
305
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + `
306
+ `);
307
+ await writeFile(join(destination, "disposabl.json"), JSON.stringify(config, null, 2) + `
63
308
  `);
64
- return dir;
309
+ } catch (error) {
310
+ await rm(destination, { recursive: true, force: true });
311
+ throw error;
312
+ }
313
+ return folders;
65
314
  }
66
- // cli/package.json
67
- var version = "0.0.3";
68
-
69
- // cli/index.ts
70
- var help = `Usage: create-disposabl-app <appname> [options]
71
315
 
72
- Create a Disposabl app from the bundled starter template.
316
+ // index.ts
317
+ var jsonOutput = process.argv.slice(2).includes("--json");
318
+ var program = new Command("create-disposabl-app").description("Create an empty Disposabl starter. Web is always included.").version(version, "-v, --version").argument("[appname]", "App name and new directory").option("--platforms <ids>", "Comma-separated platform IDs; Web is always included").option("--features <ids>", "Comma-separated feature IDs, or none (default: none)").option("-y, --yes", "Skip prompts; default to Web only and no features").option("--skip-install", "Create files without running bun install").option("--list-options", "List every platform and feature, including disabled choices").option("--json", "Skip prompts and emit a JSON result or error; install logs go to stderr").helpOption("-h, --help").addHelpText("after", `
319
+ Platforms:
320
+ web (required), android, ios, windows, linux, macos, mcp,
321
+ openai-plugin, claude-code-plugin
322
+ Either plugin automatically includes MCP.
323
+ Disabled: api, cli, python-sdk, typescript-sdk
73
324
 
74
- Options:
75
- -h, --help Show this help message
76
- -v, --version Show the CLI version
77
- --skip-install Copy the starter without running bun install
325
+ Features:
326
+ docs, blog, changelog, authentication
327
+ Authentication currently records a selection only.
78
328
 
79
329
  Examples:
80
- bun create disposabl-app@latest my-app
81
- npx create-disposabl-app@latest my-app
82
- npx create-disposabl-app@latest --help
330
+ bun create disposabl-app@latest
331
+ bun create disposabl-app@latest my-app --yes
332
+ bun create disposabl-app@latest my-app --platforms web,android,ios,mcp --features docs,authentication --yes
333
+ bun create disposabl-app@latest my-app --platforms web --features none --skip-install --json
334
+ bun create disposabl-app@latest --list-options --json
83
335
 
84
- The generated app uses Bun. Install it from https://bun.com.
85
- `;
86
- try {
87
- const { values, positionals } = parseArgs({
88
- allowPositionals: true,
89
- options: {
90
- help: { type: "boolean", short: "h" },
91
- version: { type: "boolean", short: "v" },
92
- "skip-install": { type: "boolean" }
93
- }
336
+ Without a TTY, the CLI never prompts. Supply an app name.
337
+ Platform and feature choices are saved in disposabl.json.
338
+ App folders contain empty package.json files. No appId or Convex deployment is created.
339
+ `).configureOutput({ writeErr: () => {} }).exitOverride();
340
+
341
+ class Cancelled extends Error {
342
+ }
343
+ function answer(value) {
344
+ if (p2.isCancel(value))
345
+ throw new Cancelled("Cancelled. No files were created.");
346
+ return value;
347
+ }
348
+ function showOptions(json) {
349
+ if (json) {
350
+ console.log(JSON.stringify({
351
+ platforms,
352
+ platformDependencies,
353
+ features,
354
+ defaults: { platforms: ["web"], features: [] },
355
+ folders: {
356
+ web: "apps/web",
357
+ android: "apps/mobile",
358
+ ios: "apps/mobile",
359
+ windows: "apps/desktop",
360
+ linux: "apps/desktop",
361
+ macos: "apps/desktop",
362
+ docs: "apps/docs",
363
+ blog: "apps/blog",
364
+ changelog: "apps/changelog"
365
+ },
366
+ configOnly: [
367
+ "mcp",
368
+ "openai-plugin",
369
+ "claude-code-plugin",
370
+ "authentication"
371
+ ]
372
+ }, null, 2));
373
+ return;
374
+ }
375
+ console.log(`Platforms
376
+ `);
377
+ for (const option of platforms)
378
+ console.log(" " + option.id.padEnd(21) + option.label.padEnd(20) + option.status);
379
+ console.log(`
380
+ OpenAI Plugin and Claude Code Plugin automatically include MCP.`);
381
+ console.log(`
382
+ Features
383
+ `);
384
+ for (const option of features)
385
+ console.log(" " + option.id.padEnd(21) + option.hint);
386
+ console.log(`
387
+ Defaults: Web only; no features. Use --platforms and --features with the IDs above.`);
388
+ }
389
+ async function install(directory, interactive) {
390
+ await new Promise((done, reject) => {
391
+ const child = spawn("bun", ["install"], {
392
+ cwd: directory,
393
+ stdio: ["ignore", "pipe", "pipe"],
394
+ env: { ...process.env, NODE_ENV: "development" }
395
+ });
396
+ let detail = "";
397
+ const onData = (chunk) => {
398
+ detail = (detail + chunk.toString()).slice(-4000);
399
+ if (!interactive)
400
+ process.stderr.write(chunk);
401
+ };
402
+ child.stdout.on("data", onData);
403
+ child.stderr.on("data", onData);
404
+ child.once("error", reject);
405
+ child.once("close", (code) => code === 0 ? done() : reject(new Error("bun install failed. The project was created at " + directory + `. Run bun install there to retry.
406
+ ` + detail.trim())));
94
407
  });
95
- if (values.help)
96
- console.log(help);
97
- else if (values.version)
98
- console.log(version);
99
- else if (positionals.length !== 1) {
100
- console.error(help);
101
- process.exitCode = 1;
408
+ }
409
+ async function main() {
410
+ program.parse();
411
+ const options = program.opts();
412
+ if (options.listOptions)
413
+ return showOptions(Boolean(options.json));
414
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !p2.isCI() && !options.yes && !options.json);
415
+ let chosenPlatforms = selectPlatforms(options.platforms);
416
+ let chosenFeatures = selectFeatures(options.features);
417
+ let name = program.args[0];
418
+ if (interactive) {
419
+ if (!name)
420
+ name = answer(await p2.text({
421
+ message: "App name",
422
+ placeholder: "my-app",
423
+ defaultValue: "my-app",
424
+ validate: (value) => validateName(value ?? "") || (existsSync2(resolve(value ?? "")) ? "That directory already exists. Choose a new name." : undefined)
425
+ }));
426
+ }
427
+ if (!name)
428
+ throw new Error("Provide an app name in non-interactive mode. Example: bun create disposabl-app@latest my-app --platforms web --features none --yes");
429
+ const invalidName = validateName(name);
430
+ if (invalidName)
431
+ throw new Error(invalidName);
432
+ const directory = resolve(name);
433
+ if (existsSync2(directory))
434
+ throw new Error("Directory already exists: " + directory + ". Choose a new name; existing files are never overwritten.");
435
+ if (interactive && options.platforms === undefined) {
436
+ const selected = answer(await createPlatformPrompt().prompt() ?? []);
437
+ chosenPlatforms = selectPlatforms(selected.length ? selected.join(",") : "web");
438
+ }
439
+ if (interactive && options.features === undefined) {
440
+ const selected = answer(await p2.multiselect({
441
+ message: "Choose features (optional)",
442
+ options: features.map((feature) => ({
443
+ value: feature.id,
444
+ label: feature.label,
445
+ hint: feature.hint
446
+ })),
447
+ required: false
448
+ }));
449
+ chosenFeatures = selectFeatures(selected.length ? selected.join(",") : "none");
450
+ }
451
+ const config = {
452
+ version,
453
+ platforms: chosenPlatforms,
454
+ features: chosenFeatures
455
+ };
456
+ const setup = options.json ? undefined : projectSetup(name, config);
457
+ let installationStarted = false;
458
+ try {
459
+ if (!options.skipInstall && spawnSync("bun", ["--version"], { stdio: "ignore" }).status !== 0)
460
+ throw new Error("Install Bun first, or pass --skip-install to create the files only.");
461
+ const template = fileURLToPath(new URL("./templates/default", import.meta.url));
462
+ await createProject(template, directory, name, config);
463
+ if (!options.skipInstall) {
464
+ installationStarted = true;
465
+ setup?.installing();
466
+ await install(directory, Boolean(setup?.live));
467
+ }
468
+ setup?.finish({
469
+ installation: options.skipInstall ? "Skipped" : "Complete"
470
+ });
471
+ } catch (error) {
472
+ setup?.finish({
473
+ installation: installationStarted ? "Failed" : "Not started"
474
+ });
475
+ throw error;
476
+ }
477
+ const folders = selectedFolders(config);
478
+ if (options.json) {
479
+ console.log(JSON.stringify({
480
+ status: "created",
481
+ name,
482
+ directory,
483
+ configFile: "disposabl.json",
484
+ ...config,
485
+ folders,
486
+ installed: !options.skipInstall
487
+ }, null, 2));
102
488
  } else {
103
- await createApp(positionals[0], { skipInstall: values["skip-install"] });
489
+ console.log(`
490
+ Next steps
491
+
492
+ cd ` + name + `
493
+ bun run dev
494
+ `);
104
495
  }
496
+ }
497
+ try {
498
+ await main();
105
499
  } catch (error) {
106
- console.error(error instanceof Error ? error.message : String(error));
107
- process.exitCode = 1;
500
+ if (error instanceof CommanderError && error.exitCode === 0) {
501
+ process.exitCode = 0;
502
+ } else if (error instanceof Cancelled) {
503
+ p2.cancel(error.message);
504
+ process.exitCode = 130;
505
+ } else {
506
+ const message = error instanceof Error ? error.message : String(error);
507
+ if (jsonOutput)
508
+ console.log(JSON.stringify({ status: "error", message }));
509
+ else
510
+ console.error("Error: " + message);
511
+ process.exitCode = 1;
512
+ }
108
513
  }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Disposabl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -2,3 +2,5 @@ node_modules/
2
2
  .env
3
3
  .env.*
4
4
  !.env.example
5
+ **/dist/
6
+ .turbo/
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "disposabl-app",
3
- "version": "0.0.1",
4
3
  "private": true,
5
4
  "type": "module",
5
+ "packageManager": "bun@1.4.4",
6
+ "workspaces": [
7
+ "apps/*",
8
+ "packages/*"
9
+ ],
6
10
  "scripts": {
7
- "dev": "bun --hot index.ts"
11
+ "dev": "bun run --parallel --if-present --filter './apps/*' dev"
8
12
  }
9
13
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "create-disposabl-app",
3
- "version": "0.0.3",
4
- "description": "Create a Disposabl app with Bun",
3
+ "version": "0.1.0",
4
+ "description": "Create a Disposabl app",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/disposabl-dev/create-disposabl-app.git",
10
- "directory": "cli"
10
+ "directory": "packages/create-disposabl-app"
11
11
  },
12
12
  "bin": {
13
13
  "create-disposabl-app": "dist/index.js"
@@ -17,12 +17,33 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "engines": {
20
- "node": ">=22"
20
+ "node": ">=22.12.0"
21
21
  },
22
+ "packageManager": "bun@1.4.4",
22
23
  "scripts": {
23
- "build": "bun build.ts"
24
+ "build": "bun build.ts",
25
+ "typecheck": "bun check",
26
+ "lint": "eslint .",
27
+ "test": "bun test tests",
28
+ "format": "bun fmt"
24
29
  },
25
30
  "publishConfig": {
26
31
  "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@clack/core": "1.5.1",
35
+ "@clack/prompts": "1.8.1",
36
+ "commander": "15.0.0",
37
+ "picocolors": "1.1.1"
38
+ },
39
+ "devDependencies": {
40
+ "@eslint/js": "10.0.1",
41
+ "@types/bun": "1.4.4",
42
+ "@types/semver": "7.8.0",
43
+ "eslint": "10.13.0",
44
+ "jiti": "2.7.0",
45
+ "semver": "7.8.5",
46
+ "typescript": "6.0.3",
47
+ "typescript-eslint": "8.72.1"
27
48
  }
28
49
  }
package/README.md DELETED
@@ -1,23 +0,0 @@
1
- # Create Disposabl app
2
-
3
- Create an app with [Bun](https://bun.com):
4
-
5
- ```sh
6
- bun create disposabl-app@latest my-app
7
- cd my-app
8
- bun run dev
9
- ```
10
-
11
- Or use Node.js 22 or later:
12
-
13
- ```sh
14
- npx create-disposabl-app@latest my-app
15
- npx create-disposabl-app@latest -h
16
- npx create-disposabl-app@latest -v
17
- ```
18
-
19
- Options: `-h` / `--help`, `-v` / `--version`, and `--skip-install`.
20
-
21
- The starter is bundled in the package. The CLI copies only its files, installs dependencies with Bun, and prints the next commands. Existing folders are never overwritten. Installing dependencies and running the app require Bun; help, version, and `--skip-install` work without it. The starter is an `index.ts` using `Bun.serve`.
22
-
23
- The standard CLI uses Node.js 22 or later. In a Bun-only environment, run `bunx --bun create-disposabl-app@latest my-app`.
@@ -1,9 +0,0 @@
1
- # Disposabl app
2
-
3
- Start the development server with [Bun](https://bun.com):
4
-
5
- ```sh
6
- bun run dev
7
- ```
8
-
9
- Open http://localhost:3000. Edit `index.ts` to build your app.
@@ -1,8 +0,0 @@
1
- const server = Bun.serve({
2
- port: process.env.PORT ?? 3000,
3
- fetch() {
4
- return new Response("Your Disposabl app is ready.\n");
5
- },
6
- });
7
-
8
- console.log(`Your app is running at ${server.url}`);