create-disposabl-app 0.1.2 → 0.2.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,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // index.ts
4
- import { spawn, spawnSync } from "node:child_process";
5
- import { existsSync as existsSync2 } from "node:fs";
4
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
5
+ import { lstatSync } from "node:fs";
6
6
  import { resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { Command, CommanderError } from "commander";
9
9
  import * as p2 from "@clack/prompts";
10
10
  // package.json
11
- var version = "0.1.2";
11
+ var version = "0.2.0";
12
12
 
13
13
  // options.ts
14
14
  var platforms = [
@@ -219,20 +219,50 @@ function setupTable(name, config, status, columns = 80) {
219
219
  function projectSetup(name, config) {
220
220
  const live = Boolean(process.stdout.isTTY);
221
221
  const status = { installation: "Pending" };
222
- let lines = 0;
222
+ let previous = [];
223
+ let columns = process.stdout.columns;
223
224
  let started;
224
225
  let timer;
225
226
  function render() {
226
- if (live && lines) {
227
- moveCursor(process.stdout, 0, -lines);
227
+ const frame = setupTable(name, config, status, process.stdout.columns).split(`
228
+ `);
229
+ if (live && previous.length === frame.length && columns === process.stdout.columns) {
230
+ let update = "";
231
+ for (let row = 0;row < frame.length; row++) {
232
+ const before = previous[row];
233
+ const after = frame[row];
234
+ if (before === after)
235
+ continue;
236
+ let start = 0;
237
+ let end = after.length;
238
+ if (before.length === after.length && !before.includes("\x1B") && !after.includes("\x1B")) {
239
+ while (start < end && before[start] === after[start])
240
+ start++;
241
+ while (end > start && before[end - 1] === after[end - 1])
242
+ end--;
243
+ }
244
+ const distance = frame.length - row;
245
+ update += `\x1B[${distance}A\x1B[${start + 1}G`;
246
+ if (start === 0)
247
+ update += "\x1B[2K";
248
+ update += after.slice(start, end);
249
+ update += `\x1B[${distance}B\x1B[1G`;
250
+ }
251
+ if (update)
252
+ process.stdout.write(update);
253
+ previous = frame;
254
+ return;
255
+ }
256
+ if (live && previous.length) {
257
+ moveCursor(process.stdout, 0, -previous.length);
228
258
  cursorTo(process.stdout, 0);
229
259
  clearScreenDown(process.stdout);
230
260
  }
231
- const frame = setupTable(name, config, status, process.stdout.columns);
232
- process.stdout.write(frame + `
261
+ process.stdout.write(frame.join(`
262
+ `) + `
233
263
  `);
234
- lines = frame.split(`
235
- `).length;
264
+ previous = frame;
265
+ columns = process.stdout.columns;
236
266
  }
237
267
  if (live)
238
268
  render();
@@ -272,7 +302,7 @@ import {
272
302
  } from "node:fs/promises";
273
303
  import { existsSync } from "node:fs";
274
304
  import { join } from "node:path";
275
- async function createProject(template, destination, name, config) {
305
+ async function createApp(template, destination, name, config, overwrite = false) {
276
306
  config = {
277
307
  ...config,
278
308
  features: selectFeatures(config.features.join(",") || "none")
@@ -283,6 +313,8 @@ async function createProject(template, destination, name, config) {
283
313
  throw new Error("The bundled starter is missing " + folder + "/package.json. Rebuild the CLI.");
284
314
  }
285
315
  }
316
+ if (overwrite)
317
+ await rm(destination, { recursive: true, force: true });
286
318
  await mkdir(destination);
287
319
  try {
288
320
  for (const entry of await readdir(template)) {
@@ -343,6 +375,7 @@ The starter includes web and backend authentication code. No appId or Convex dep
343
375
 
344
376
  class Cancelled extends Error {
345
377
  }
378
+ var pathExists = (path) => lstatSync(path, { throwIfNoEntry: false }) !== undefined;
346
379
  function answer(value) {
347
380
  if (p2.isCancel(value))
348
381
  throw new Cancelled("Cancelled. No files were created.");
@@ -420,7 +453,7 @@ async function main() {
420
453
  message: "App name",
421
454
  placeholder: "my-app",
422
455
  defaultValue: "my-app",
423
- validate: (value) => validateName(value ?? "") || (existsSync2(resolve(value ?? "")) ? "That directory already exists. Choose a new name." : undefined)
456
+ validate: (value) => validateName(value ?? "")
424
457
  }));
425
458
  }
426
459
  if (!name)
@@ -428,9 +461,25 @@ async function main() {
428
461
  const invalidName = validateName(name);
429
462
  if (invalidName)
430
463
  throw new Error(invalidName);
431
- const directory = resolve(name);
432
- if (existsSync2(directory))
433
- throw new Error("Directory already exists: " + directory + ". Choose a new name; existing files are never overwritten.");
464
+ let directory = resolve(name);
465
+ let overwrite = false;
466
+ if (pathExists(directory)) {
467
+ overwrite = interactive && answer(await p2.confirm({
468
+ message: `"${name}" already exists. Delete its contents and replace it?`,
469
+ initialValue: false
470
+ }));
471
+ if (!overwrite) {
472
+ const requestedName = name;
473
+ let suffix = 2;
474
+ do {
475
+ const ending = `-${suffix++}`;
476
+ name = requestedName.slice(0, 214 - ending.length) + ending;
477
+ directory = resolve(name);
478
+ } while (pathExists(directory));
479
+ if (!options.json)
480
+ p2.log.info(`Using "${name}" instead.`);
481
+ }
482
+ }
434
483
  if (interactive && options.platforms === undefined) {
435
484
  const selected = answer(await createPlatformPrompt().prompt() ?? []);
436
485
  chosenPlatforms = selectPlatforms(selected.length ? selected.join(",") : "web");
@@ -455,10 +504,12 @@ async function main() {
455
504
  const setup = options.json ? undefined : projectSetup(name, config);
456
505
  let installationStarted = false;
457
506
  try {
507
+ if (spawnSync("git", ["--version"], { stdio: "ignore" }).status !== 0)
508
+ throw new Error("Install Git first to create the app's initial commit.");
458
509
  if (!options.skipInstall && spawnSync("bun", ["--version"], { stdio: "ignore" }).status !== 0)
459
510
  throw new Error("Install Bun first, or pass --skip-install to create the files only.");
460
511
  const template = fileURLToPath(new URL("./templates/default", import.meta.url));
461
- await createProject(template, directory, name, config);
512
+ await createApp(template, directory, name, config, overwrite);
462
513
  if (!options.skipInstall) {
463
514
  installationStarted = true;
464
515
  setup?.installing();
@@ -473,6 +524,19 @@ async function main() {
473
524
  });
474
525
  throw error;
475
526
  }
527
+ const git = (...args) => execFileSync("git", args, { cwd: directory, stdio: "pipe" });
528
+ git("init", "--initial-branch=main");
529
+ git("add", "--all");
530
+ const hasIdentity = spawnSync("git", ["var", "GIT_AUTHOR_IDENT"], {
531
+ cwd: directory,
532
+ stdio: "ignore"
533
+ }).status === 0;
534
+ git(...hasIdentity ? [] : [
535
+ "-c",
536
+ "user.name=create-disposabl-app",
537
+ "-c",
538
+ "user.email=create-disposabl-app@users.noreply.github.com"
539
+ ], "-c", "commit.gpgsign=false", "commit", "-m", "Initial commit");
476
540
  const folders = selectedFolders(config);
477
541
  if (options.json) {
478
542
  console.log(JSON.stringify({
@@ -0,0 +1,100 @@
1
+ name: CI and deploy
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ types: [opened, synchronize, reopened]
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ pipeline:
19
+ if: github.event.deleted != true && github.repository != 'disposabl-dev/create-disposabl-app'
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 30
22
+ steps:
23
+ - name: Check out repository
24
+ uses: actions/checkout@v6
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - name: Install Node.js
29
+ uses: actions/setup-node@v6
30
+ with:
31
+ node-version: "24"
32
+ package-manager-cache: false
33
+
34
+ - name: Install Bun
35
+ uses: oven-sh/setup-bun@v2
36
+ with:
37
+ bun-version: "1.4.4"
38
+
39
+ - name: Install dependencies
40
+ run: bun install --frozen-lockfile
41
+
42
+ - name: Typecheck
43
+ run: bun run typecheck
44
+
45
+ - name: Lint
46
+ run: bun run lint
47
+
48
+ - name: Check formatting
49
+ run: bun fmt --check
50
+
51
+ - name: Test
52
+ # The starter currently has no tests. Run any tests added later.
53
+ run: bun test --pass-with-no-tests
54
+
55
+ - name: Build
56
+ env:
57
+ VITE_CONVEX_URL: https://ci.invalid
58
+ VITE_CONVEX_SITE_URL: https://ci.invalid
59
+ run: bun run build
60
+
61
+ - name: Deploy production
62
+ id: production
63
+ if: github.event_name == 'push'
64
+ working-directory: packages/backend
65
+ env:
66
+ CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
67
+ run: |
68
+ bun x --no-install convex deploy --yes \
69
+ --cmd-url-env-var-name VITE_CONVEX_URL \
70
+ --cmd '
71
+ bun x --no-install --package @convex-dev/auth auth --keys-only --prod &&
72
+ VITE_CONVEX_SITE_URL="$CONVEX_SITE_URL" bun run --cwd ../.. build &&
73
+ echo "url=$CONVEX_SITE_URL" >> "$GITHUB_OUTPUT"
74
+ '
75
+ bun x --no-install static-hosting upload --prod --no-spa --dist ../../apps/web/dist/client
76
+
77
+ - name: Deploy preview
78
+ id: preview
79
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
80
+ working-directory: packages/backend
81
+ env:
82
+ CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY_PREVIEW }}
83
+ PREVIEW_NAME: ${{ github.head_ref }}
84
+ run: |
85
+ bun x --no-install convex deploy --yes --preview-name "$PREVIEW_NAME" \
86
+ --cmd-url-env-var-name VITE_CONVEX_URL \
87
+ --cmd '
88
+ bun x --no-install --package @convex-dev/auth auth --keys-only --preview-name "$PREVIEW_NAME" &&
89
+ VITE_CONVEX_SITE_URL="$CONVEX_SITE_URL" bun run --cwd ../.. build &&
90
+ echo "url=$CONVEX_SITE_URL" >> "$GITHUB_OUTPUT"
91
+ '
92
+ bun x --no-install static-hosting upload --preview-name "$PREVIEW_NAME" --no-spa --dist ../../apps/web/dist/client
93
+
94
+ - name: Show deployment URL
95
+ if: steps.production.outcome == 'success' || steps.preview.outcome == 'success'
96
+ env:
97
+ DEPLOYMENT_URL: ${{ steps.production.outputs.url || steps.preview.outputs.url }}
98
+ run: |
99
+ printf 'Deployed app: %s\n' "$DEPLOYMENT_URL"
100
+ printf '### Deployment\n\n[Open app](%s)\n' "$DEPLOYMENT_URL" >> "$GITHUB_STEP_SUMMARY"