@cellajs/create-cella 0.2.3 → 0.3.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/README.md CHANGED
@@ -4,7 +4,7 @@ CLI tool to scaffold a new Cella project from the template.
4
4
 
5
5
  ## Overview
6
6
 
7
- This CLI creates a new Cella project by downloading the latest template, setting up your development environment, and configuring git with upstream tracking for future syncs.
7
+ This CLI creates a new Cella project by downloading the latest template, scaffolding your project, and configuring git with upstream tracking for future syncs. It runs no shell commands and requires no `git` binary — git operations use [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) and the template is fetched and extracted in-process.
8
8
 
9
9
  ## Usage
10
10
 
@@ -24,15 +24,23 @@ Running without arguments starts interactive mode, prompting for:
24
24
  pnpm create @cellajs/cella [directory] [options]
25
25
  ```
26
26
 
27
+ | Option | Description |
28
+ | --- | --- |
29
+ | `--template <path>` | Use a custom template (local path or `github:user/repo`) |
30
+ | `--port-offset <number>` | Set the port offset (0-490 in steps of 10) |
31
+ | `--admin-email <email>` | Set the admin email for the initial seed user |
32
+ | `-v, --version` | Output the current version |
33
+ | `-h, --help` | Display the help message |
34
+
27
35
  ## What It Does
28
36
 
29
- 1. Downloads latest Cella template via [giget](https://github.com/unjs/giget)
30
- 2. Cleans template files (removes cella-specific docs, configs)
31
- 3. Installs dependencies with `pnpm install`
32
- 4. Generates initial database migrations
33
- 5. Initializes git repository with initial commit
34
- 6. Creates optional working branch
35
- 7. Adds Cella as upstream remote for future syncs
37
+ 1. Downloads the latest Cella template as a GitHub tarball and extracts it in-process (no `git` binary, no shell)
38
+ 2. Cleans template files (removes cella-specific docs, configs) and interpolates the project config
39
+ 3. Initializes a git repository with an initial commit via isomorphic-git
40
+ 4. Creates an optional working branch
41
+ 5. Adds Cella as the upstream remote for future syncs
42
+
43
+ Dependency installation and migration generation are **not** run automatically — after scaffolding, run `pnpm install`, then `pnpm generate` (the CLI prints these next steps on success).
36
44
 
37
45
  ## Development
38
46
 
@@ -45,6 +53,9 @@ pnpm ts
45
53
  # Lint
46
54
  pnpm lint:fix
47
55
 
56
+ # Try it out
57
+ pnpm try
58
+
48
59
  # Run tests
49
60
  pnpm test
50
61
 
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import pc from "picocolors";
12
12
  // package.json
13
13
  var package_default = {
14
14
  name: "@cellajs/create-cella",
15
- version: "0.2.3",
15
+ version: "0.3.0",
16
16
  private: false,
17
17
  license: "MIT",
18
18
  description: "Cella is a TypeScript template to create web apps with sync and offline capabilities.",
@@ -21,8 +21,7 @@ var package_default = {
21
21
  },
22
22
  repository: {
23
23
  type: "git",
24
- url: "https://github.com/cellajs/cella",
25
- directory: "cli/create-cella"
24
+ url: "https://github.com/cellajs/create-cella"
26
25
  },
27
26
  homepage: "https://cellajs.com",
28
27
  author: "CellaJS <info@cellajs.com>",
@@ -45,7 +44,7 @@ var package_default = {
45
44
  },
46
45
  scripts: {
47
46
  start: "tsx ./src/create-cella-cli.ts",
48
- try: "tsx ./src/create-cella-cli.ts ../../../cella-try-app --template ../.. --skip-install",
47
+ try: "tsx ./src/create-cella-cli.ts ../cella-try-app",
49
48
  clean: "rimraf ./dist",
50
49
  build: "tsup",
51
50
  "test-build": "pnpm run build && node index.js",
@@ -55,26 +54,58 @@ var package_default = {
55
54
  lint: "biome check .",
56
55
  "lint:fix": "biome check --write .",
57
56
  test: "vitest run",
58
- "test:release": "pnpm run build && vitest run tests/release-smoke.test.ts",
59
- "test:watch": "vitest"
57
+ "test:release": "pnpm run build && vitest run --config vitest.release.config.ts",
58
+ "test:watch": "vitest",
59
+ prepare: "lefthook install"
60
60
  },
61
61
  dependencies: {
62
62
  "@inquirer/prompts": "^8.5.2",
63
63
  commander: "^15.0.0",
64
- giget: "^3.3.0",
65
- "nano-spawn": "^2.0.0",
64
+ ignore: "^7.0.5",
65
+ "isomorphic-git": "^1.38.5",
66
+ nanotar: "^0.3.0",
66
67
  ora: "^9.4.1",
67
68
  picocolors: "^1.1.1",
68
69
  "validate-npm-package-name": "^8.0.0"
69
70
  },
70
71
  devDependencies: {
72
+ "@biomejs/biome": "^2.5.1",
73
+ "@commitlint/cli": "^21.1.0",
74
+ "@commitlint/config-conventional": "^21.1.0",
75
+ "@evilmartians/lefthook": "^2.1.9",
71
76
  "@types/node": "24.10.13",
72
77
  "@types/validate-npm-package-name": "^4.0.2",
73
78
  "@typescript/native-preview": "7.0.0-dev.20260628.1",
79
+ rimraf: "^6.1.3",
74
80
  tsup: "^8.5.1",
75
81
  tsx: "^4.22.4",
76
82
  typescript: "^6.0.3",
77
83
  vitest: "^4.1.9"
84
+ },
85
+ packageManager: "pnpm@11.8.0",
86
+ commitlint: {
87
+ extends: [
88
+ "@commitlint/config-conventional"
89
+ ],
90
+ rules: {
91
+ "type-enum": [
92
+ 2,
93
+ "always",
94
+ [
95
+ "feat",
96
+ "fix",
97
+ "perf",
98
+ "refactor",
99
+ "revert",
100
+ "docs",
101
+ "chore",
102
+ "build",
103
+ "ci",
104
+ "style",
105
+ "test"
106
+ ]
107
+ ]
108
+ }
78
109
  }
79
110
  };
80
111
 
@@ -101,10 +132,10 @@ var TO_COPY = {
101
132
  };
102
133
  var PLACEHOLDER_CONFIG = "configs/default-config.ts.template";
103
134
  async function generateEnvFromExample(examplePath, replacements) {
104
- const { readFile: readFile3 } = await import("fs/promises");
135
+ const { readFile: readFile4 } = await import("fs/promises");
105
136
  let content;
106
137
  try {
107
- content = await readFile3(examplePath, "utf8");
138
+ content = await readFile4(examplePath, "utf8");
108
139
  } catch {
109
140
  return null;
110
141
  }
@@ -188,45 +219,36 @@ ${nameEntry}${body}
188
219
  }
189
220
 
190
221
  // src/create.ts
191
- import { execFileSync } from "child_process";
192
222
  import { existsSync } from "fs";
193
- import { cp, mkdir, rm } from "fs/promises";
194
- import { dirname as dirname2, isAbsolute, join as join2, relative as relative2, resolve as resolve2 } from "path";
223
+ import { cp, mkdir as mkdir2, rm } from "fs/promises";
224
+ import { dirname as dirname3, isAbsolute, join as join4, relative as relative2, resolve as resolve2 } from "path";
195
225
  import { checkbox } from "@inquirer/prompts";
196
- import { downloadTemplate } from "giget";
197
226
  import pc5 from "picocolors";
198
227
 
199
228
  // src/utils/git/command.ts
200
- import { execFile } from "child_process";
201
- import { promisify } from "util";
202
- var execFileAsync = promisify(execFile);
203
- async function runGitCommand(args, repoPath, options2 = {}) {
204
- const gitArgs = repoPath ? ["-C", repoPath, ...args] : args;
205
- const env = {
206
- ...process.env,
207
- ...options2.skipEditor ? { GIT_EDITOR: "true" } : {}
208
- };
209
- const maxBuffer = options2.maxBuffer ?? 10 * 1024 * 1024;
210
- const { stdout } = await execFileAsync("git", gitArgs, { env, maxBuffer });
211
- return stdout.trim();
212
- }
229
+ import fs from "fs";
230
+ import git from "isomorphic-git";
231
+ var INITIAL_AUTHOR = { name: "cella", email: "info@cellajs.com" };
213
232
  async function gitInit(repoPath) {
214
- return runGitCommand(["init"], repoPath);
233
+ await git.init({ fs, dir: repoPath, defaultBranch: "main" });
215
234
  }
216
235
  async function gitAddAll(repoPath) {
217
- return runGitCommand(["add", "."], repoPath);
236
+ await git.add({ fs, dir: repoPath, filepath: "." });
218
237
  }
219
238
  async function gitCommit(repoPath, message) {
220
- return runGitCommand(["commit", "--quiet", "--no-verify", "-m", message], repoPath);
239
+ return git.commit({ fs, dir: repoPath, message, author: INITIAL_AUTHOR });
221
240
  }
222
241
  async function gitRemoteGetUrl(repoPath, remoteName) {
223
- return runGitCommand(["remote", "get-url", remoteName], repoPath);
242
+ const remotes = await git.listRemotes({ fs, dir: repoPath });
243
+ const match = remotes.find((r) => r.remote === remoteName);
244
+ if (!match) throw new Error(`No such remote '${remoteName}'`);
245
+ return match.url;
224
246
  }
225
247
  async function gitRemoteAdd(repoPath, remoteName, remoteUrl) {
226
- return runGitCommand(["remote", "add", remoteName, remoteUrl], repoPath);
248
+ await git.addRemote({ fs, dir: repoPath, remote: remoteName, url: remoteUrl });
227
249
  }
228
250
  async function gitRemoteRemove(repoPath, remoteName) {
229
- return runGitCommand(["remote", "remove", remoteName], repoPath);
251
+ await git.deleteRemote({ fs, dir: repoPath, remote: remoteName });
230
252
  }
231
253
 
232
254
  // src/add-remote.ts
@@ -286,7 +308,7 @@ function parsePortOffset(value) {
286
308
  }
287
309
  var directory = null;
288
310
  var packageManager = "pnpm";
289
- var command = new Command(NAME).version(VERSION, "-v, --version", `output the current version of ${NAME}`).argument("[directory]", "the directory name for the new project").usage("[directory] [options]").helpOption("-h, --help", "display this help message").option("--template <path>", "use a custom template (local path or github:user/repo)").option("--port-offset <number>", "set the port offset (0-490 in steps of 10)", parsePortOffset).option("--admin-email <email>", "set the admin email for the initial seed user").option("--skip-install", "scaffold only: skip dependency installation and migration generation").action((name) => {
311
+ var command = new Command(NAME).version(VERSION, "-v, --version", `output the current version of ${NAME}`).argument("[directory]", "the directory name for the new project").usage("[directory] [options]").helpOption("-h, --help", "display this help message").option("--template <path>", "use a custom template (local path or github:user/repo)").option("--port-offset <number>", "set the port offset (0-490 in steps of 10)", parsePortOffset).option("--admin-email <email>", "set the admin email for the initial seed user").action((name) => {
290
312
  const trimmedName = typeof name === "string" ? name.trim() : name;
291
313
  if (trimmedName) {
292
314
  const validation = validateProjectName(basename(resolve(trimmedName)));
@@ -337,6 +359,8 @@ function showSuccess(projectName, _targetFolder, relativePath, needsCd, packageM
337
359
  if (needsCd) {
338
360
  console.info(`${pc2.dim("$")} cd ${pc2.cyan(relativePath)}`);
339
361
  }
362
+ console.info(`${pc2.dim("$")} ${pc2.cyan(`${packageManager2} install`)}`);
363
+ console.info(`${pc2.dim("$")} ${pc2.cyan(`${packageManager2} generate`)}`);
340
364
  console.info(
341
365
  `${pc2.dim("$")} ${pc2.cyan(`${packageManager2} docker`)} ${pc2.dim("&&")} ${pc2.cyan(`${packageManager2} seed`)} ${pc2.dim("&&")} ${pc2.cyan(`${packageManager2} dev`)}`
342
366
  );
@@ -349,7 +373,7 @@ function showSuccess(projectName, _targetFolder, relativePath, needsCd, packageM
349
373
  }
350
374
 
351
375
  // src/utils/clean-template.ts
352
- import fs from "fs/promises";
376
+ import fs2 from "fs/promises";
353
377
  import path from "path";
354
378
  import { fileURLToPath } from "url";
355
379
  import pc3 from "picocolors";
@@ -359,7 +383,7 @@ async function resolvePlaceholderConfigPath() {
359
383
  const candidates = [path.resolve(here, "../..", PLACEHOLDER_CONFIG), path.resolve(here, "..", PLACEHOLDER_CONFIG)];
360
384
  for (const candidate of candidates) {
361
385
  try {
362
- await fs.access(candidate);
386
+ await fs2.access(candidate);
363
387
  return candidate;
364
388
  } catch {
365
389
  }
@@ -391,12 +415,12 @@ async function cleanTemplate({
391
415
  backendReplacements
392
416
  );
393
417
  if (backendEnv) {
394
- await fs.writeFile(path.resolve(targetFolder, "backend/.env"), backendEnv, "utf8");
418
+ await fs2.writeFile(path.resolve(targetFolder, "backend/.env"), backendEnv, "utf8");
395
419
  }
396
420
  const envConfigs = generateEnvConfigs(projectName, displayName, portOffset);
397
421
  await Promise.all(
398
422
  Object.entries(envConfigs).map(
399
- ([filePath, content]) => fs.writeFile(path.resolve(targetFolder, filePath), content, "utf8")
423
+ ([filePath, content]) => fs2.writeFile(path.resolve(targetFolder, filePath), content, "utf8")
400
424
  )
401
425
  );
402
426
  await Promise.all(
@@ -419,27 +443,27 @@ async function cleanTemplate({
419
443
  });
420
444
  }
421
445
  async function removeFolderContents(folderPath) {
422
- const files = await fs.readdir(folderPath);
446
+ const files = await fs2.readdir(folderPath);
423
447
  await Promise.all(
424
448
  files.map(async (file) => {
425
449
  const filePath = path.join(folderPath, file);
426
- const stat = await fs.lstat(filePath);
450
+ const stat = await fs2.lstat(filePath);
427
451
  if (stat.isDirectory()) {
428
- await fs.rm(filePath, { recursive: true, force: true });
452
+ await fs2.rm(filePath, { recursive: true, force: true });
429
453
  } else {
430
- await fs.rm(filePath);
454
+ await fs2.rm(filePath);
431
455
  }
432
456
  })
433
457
  );
434
458
  }
435
459
  async function removeFileOrFolder(pathToRemove) {
436
- await fs.rm(pathToRemove, { recursive: true, force: true });
460
+ await fs2.rm(pathToRemove, { recursive: true, force: true });
437
461
  }
438
462
  async function copyFile(src, dest) {
439
463
  try {
440
- await fs.access(src);
441
- await fs.mkdir(path.dirname(dest), { recursive: true });
442
- await fs.copyFile(src, dest);
464
+ await fs2.access(src);
465
+ await fs2.mkdir(path.dirname(dest), { recursive: true });
466
+ await fs2.copyFile(src, dest);
443
467
  } catch (err) {
444
468
  if (err.code === "ENOENT") {
445
469
  console.info(`
@@ -454,10 +478,10 @@ async function applyPlaceholderConfig(targetFolder, projectName) {
454
478
  const src = await resolvePlaceholderConfigPath();
455
479
  const dest = path.resolve(targetFolder, "./shared/config/config.default.ts");
456
480
  try {
457
- let content = await fs.readFile(src, "utf8");
481
+ let content = await fs2.readFile(src, "utf8");
458
482
  content = content.replaceAll("__project_name__", displayName);
459
483
  content = content.replaceAll("__project_slug__", projectName);
460
- await fs.writeFile(dest, content, "utf8");
484
+ await fs2.writeFile(dest, content, "utf8");
461
485
  } catch (err) {
462
486
  if (err.code === "ENOENT") {
463
487
  console.info(`
@@ -468,6 +492,61 @@ ${warningMark} Placeholder config "${src}" not found > Skip`);
468
492
  }
469
493
  }
470
494
 
495
+ // src/utils/download-template.ts
496
+ import { mkdir, writeFile } from "fs/promises";
497
+ import { dirname, join } from "path";
498
+ import { parseTarGzip } from "nanotar";
499
+ function parseGithubRepo(template) {
500
+ const [owner, repo] = template.replace(/^github:/, "").split("/");
501
+ if (!owner || !repo) {
502
+ throw new Error(`Invalid template '${template}'. Expected 'github:owner/repo'.`);
503
+ }
504
+ return { owner, repo };
505
+ }
506
+ async function downloadGithubTemplate(template, ref, targetFolder) {
507
+ const { owner, repo } = parseGithubRepo(template);
508
+ const url = `https://codeload.github.com/${owner}/${repo}/tar.gz/${ref}`;
509
+ const response = await fetch(url);
510
+ if (!response.ok) {
511
+ throw new Error(`Failed to download template ${owner}/${repo}@${ref}: ${response.status} ${response.statusText}`);
512
+ }
513
+ const archive = new Uint8Array(await response.arrayBuffer());
514
+ const entries = await parseTarGzip(archive);
515
+ for (const entry of entries) {
516
+ if (entry.type !== "file" || !entry.data) continue;
517
+ const relativePath = entry.name.replace(/^[^/]+\//, "");
518
+ if (!relativePath) continue;
519
+ const dest = join(targetFolder, relativePath);
520
+ await mkdir(dirname(dest), { recursive: true });
521
+ const mode = entry.attrs?.mode ? Number.parseInt(entry.attrs.mode, 8) : void 0;
522
+ await writeFile(dest, entry.data, mode ? { mode } : void 0);
523
+ }
524
+ }
525
+
526
+ // src/utils/list-template-files.ts
527
+ import { readdir, readFile } from "fs/promises";
528
+ import { join as join2 } from "path";
529
+ import ignore from "ignore";
530
+ async function listTemplateFiles(root) {
531
+ const ig = ignore().add([".git", "node_modules"]);
532
+ try {
533
+ ig.add(await readFile(join2(root, ".gitignore"), "utf8"));
534
+ } catch {
535
+ }
536
+ const files = [];
537
+ async function walk(relDir) {
538
+ const entries = await readdir(relDir ? join2(root, relDir) : root, { withFileTypes: true });
539
+ for (const entry of entries) {
540
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
541
+ if (ig.ignores(entry.isDirectory() ? `${rel}/` : rel)) continue;
542
+ if (entry.isDirectory()) await walk(rel);
543
+ else if (entry.isFile()) files.push(rel);
544
+ }
545
+ }
546
+ await walk("");
547
+ return files;
548
+ }
549
+
471
550
  // src/utils/progress.ts
472
551
  import ora from "ora";
473
552
  import pc4 from "picocolors";
@@ -524,45 +603,21 @@ function createProgress(title, silent = false) {
524
603
  };
525
604
  }
526
605
 
527
- // src/utils/run-package-manager-command.ts
528
- import spawn from "nano-spawn";
529
- async function runPackageManagerCommand(packageManager2, args, env = {}) {
530
- try {
531
- await spawn(packageManager2, args, {
532
- env: { ...process.env, ...env },
533
- stdio: ["pipe", "pipe", "pipe"]
534
- });
535
- } catch (error) {
536
- const err = error;
537
- throw new Error(`"${packageManager2} ${args.join(" ")}" failed ${err.stdout || ""} ${err.stderr || ""}`);
538
- }
539
- }
540
- async function install(packageManager2) {
541
- return runPackageManagerCommand(packageManager2, ["install"], {
542
- NODE_ENV: "development"
543
- });
544
- }
545
- async function generate(packageManager2) {
546
- return runPackageManagerCommand(packageManager2, ["generate"], {
547
- NODE_ENV: "development"
548
- });
549
- }
550
-
551
606
  // src/utils/scan-optional-modules.ts
552
- import { readdir, readFile } from "fs/promises";
553
- import { dirname, join, relative } from "path";
607
+ import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
608
+ import { dirname as dirname2, join as join3, relative } from "path";
554
609
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".turbo", "coverage"]);
555
610
  async function scanOptionalModules(targetFolder) {
556
611
  const files = [];
557
612
  await collectModuleFiles(targetFolder, files);
558
613
  const modules = [];
559
614
  for (const file of files) {
560
- const src = await readFile(file, "utf8");
615
+ const src = await readFile2(file, "utf8");
561
616
  if (!/optional:\s*true/.test(src)) continue;
562
617
  const name = src.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
563
618
  const description = src.match(/description:\s*['"]([^'"]+)['"]/)?.[1] ?? "";
564
619
  if (!name) continue;
565
- modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });
620
+ modules.push({ name, description, folder: relative(targetFolder, dirname2(file)) });
566
621
  }
567
622
  return modules.sort((a, b) => a.name.localeCompare(b.name));
568
623
  }
@@ -577,13 +632,13 @@ function optionalModuleFolders({ name, folder }) {
577
632
  ];
578
633
  }
579
634
  async function collectModuleFiles(dir, out) {
580
- const entries = await readdir(dir, { withFileTypes: true });
635
+ const entries = await readdir2(dir, { withFileTypes: true });
581
636
  for (const entry of entries) {
582
637
  if (entry.isDirectory()) {
583
638
  if (SKIP_DIRS.has(entry.name)) continue;
584
- await collectModuleFiles(join(dir, entry.name), out);
639
+ await collectModuleFiles(join3(dir, entry.name), out);
585
640
  } else if (entry.name.endsWith("-module.ts")) {
586
- out.push(join(dir, entry.name));
641
+ out.push(join3(dir, entry.name));
587
642
  }
588
643
  }
589
644
  }
@@ -603,7 +658,6 @@ async function create({
603
658
  templateRef,
604
659
  portOffset,
605
660
  adminEmail,
606
- skipInstall = false,
607
661
  silent = false
608
662
  }) {
609
663
  const originalCwd = process.cwd();
@@ -612,7 +666,7 @@ async function create({
612
666
  const progress = createProgress("creating project", silent);
613
667
  try {
614
668
  progress.step("creating project folder");
615
- await mkdir(targetFolder, { recursive: true });
669
+ await mkdir2(targetFolder, { recursive: true });
616
670
  process.chdir(targetFolder);
617
671
  if (isLocalTemplate) {
618
672
  progress.step("copying local template");
@@ -625,50 +679,24 @@ async function create({
625
679
  Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`
626
680
  );
627
681
  }
628
- let tracked = [];
629
- try {
630
- tracked = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
631
- cwd: sourcePath
632
- }).toString().split("\0").filter(Boolean);
633
- } catch {
634
- }
635
- if (tracked.length > 0) {
636
- for (const rel2 of tracked) {
637
- const src = join2(sourcePath, rel2);
638
- if (!existsSync(src)) continue;
639
- const dest = join2(targetFolder, rel2);
640
- await mkdir(dirname2(dest), { recursive: true });
641
- await cp(src, dest);
642
- }
643
- } else {
644
- await cp(sourcePath, targetFolder, {
645
- recursive: true,
646
- filter: (src) => !src.includes("node_modules") && !src.includes(".git")
647
- });
682
+ const tracked = await listTemplateFiles(sourcePath);
683
+ for (const rel2 of tracked) {
684
+ const src = join4(sourcePath, rel2);
685
+ if (!existsSync(src)) continue;
686
+ const dest = join4(targetFolder, rel2);
687
+ await mkdir2(dirname3(dest), { recursive: true });
688
+ await cp(src, dest);
648
689
  }
649
690
  } else {
650
- const downloadId = templateRef ? `${template}#${templateRef}` : template;
691
+ const ref = templateRef ?? "main";
651
692
  progress.step(`downloading cella template${templateRef ? ` (${templateRef})` : ""}`);
652
- await downloadTemplate(downloadId, {
653
- cwd: process.cwd(),
654
- dir: targetFolder,
655
- force: true,
656
- provider: "github"
657
- });
693
+ await downloadGithubTemplate(template, ref, targetFolder);
658
694
  }
659
695
  if (!silent) await promptOptionalModules(targetFolder);
660
696
  progress.step("cleaning template");
661
697
  const displayName = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
662
698
  await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });
663
- if (!skipInstall && !shouldSkipStep("install")) {
664
- progress.step("installing dependencies");
665
- await install(packageManager2);
666
- }
667
- if (!skipInstall && !shouldSkipStep("generate")) {
668
- progress.step("generating migrations");
669
- await generate(packageManager2);
670
- }
671
- const gitFolderPath = join2(targetFolder, ".git");
699
+ const gitFolderPath = join4(targetFolder, ".git");
672
700
  if (!shouldSkipStep("git") && !existsSync(gitFolderPath)) {
673
701
  progress.step("initializing git");
674
702
  await gitInit(targetFolder);
@@ -713,25 +741,25 @@ async function promptOptionalModules(targetFolder) {
713
741
  confirmChoice("Optional modules", keep.length ? keep.join(", ") : pc5.dim("none"));
714
742
  resumeSpinner();
715
743
  const folders = modules.filter((m) => !keep.includes(m.name)).flatMap(optionalModuleFolders);
716
- await Promise.all(folders.map((f) => rm(join2(targetFolder, f), { recursive: true, force: true })));
744
+ await Promise.all(folders.map((f) => rm(join4(targetFolder, f), { recursive: true, force: true })));
717
745
  }
718
746
 
719
747
  // src/utils/detect-used-ports.ts
720
- import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
721
- import { dirname as dirname3, join as join3 } from "path";
748
+ import { readdir as readdir3, readFile as readFile3 } from "fs/promises";
749
+ import { dirname as dirname4, join as join5 } from "path";
722
750
  async function detectUsedPorts(targetFolder) {
723
- const parentDir = dirname3(targetFolder);
751
+ const parentDir = dirname4(targetFolder);
724
752
  const used = [];
725
753
  let siblings;
726
754
  try {
727
- siblings = await readdir2(parentDir);
755
+ siblings = await readdir3(parentDir);
728
756
  } catch {
729
757
  return used;
730
758
  }
731
759
  for (const name of siblings) {
732
- const configPath = join3(parentDir, name, "shared/config/config.development.ts");
760
+ const configPath = join5(parentDir, name, "shared/config/config.development.ts");
733
761
  try {
734
- const content = await readFile2(configPath, "utf8");
762
+ const content = await readFile3(configPath, "utf8");
735
763
  const feMatch = content.match(/frontendUrl:\s*'http:\/\/localhost:(\d+)'/);
736
764
  const beMatch = content.match(/backendUrl:\s*'http:\/\/localhost:(\d+)'/);
737
765
  if (feMatch && beMatch) {
@@ -812,9 +840,9 @@ async function fetchLatestCommit(repositoryUrl, branch = "main") {
812
840
  }
813
841
 
814
842
  // src/utils/is-empty-directory.ts
815
- import { readdir as readdir3 } from "fs/promises";
843
+ import { readdir as readdir4 } from "fs/promises";
816
844
  async function isEmptyDirectory(path2) {
817
- const files = await readdir3(path2);
845
+ const files = await readdir4(path2);
818
846
  return files.length === 0 || files.length === 1 && files[0] === ".git";
819
847
  }
820
848
 
@@ -877,8 +905,7 @@ async function main() {
877
905
  templateUrl: cli.options.template,
878
906
  templateRef,
879
907
  portOffset,
880
- adminEmail,
881
- skipInstall: cli.options.skipInstall
908
+ adminEmail
882
909
  };
883
910
  await create(createOptions);
884
911
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/create-cella-cli.ts","../src/constants.ts","../package.json","../src/create.ts","../src/utils/git/command.ts","../src/add-remote.ts","../src/modules/cli/commands.ts","../src/utils/validate-project-name.ts","../src/modules/cli/display.ts","../src/utils/clean-template.ts","../src/utils/progress.ts","../src/utils/run-package-manager-command.ts","../src/utils/scan-optional-modules.ts","../src/utils/detect-used-ports.ts","../src/utils/extract-package-json-version-from-uri.ts","../src/utils/fetch-template-metadata.ts","../src/utils/is-empty-directory.ts"],"sourcesContent":["#!/usr/bin/env tsx\n\nimport { existsSync } from 'node:fs';\nimport { basename, resolve } from 'node:path';\nimport { input, select } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { TEMPLATE_URL } from '#/constants';\nimport { create } from '#/create';\nimport { type CreateOptions, cli, confirmChoice, showWelcome } from '#/modules/cli';\nimport { detectUsedPorts, findNextOffset } from '#/utils/detect-used-ports';\nimport { extractPackageJsonVersionFromUri } from '#/utils/extract-package-json-version-from-uri';\nimport { fetchLatestCommit, fetchLatestRelease } from '#/utils/fetch-template-metadata';\nimport { isEmptyDirectory } from '#/utils/is-empty-directory';\nimport { validateProjectName } from '#/utils/validate-project-name';\n\nasync function main(): Promise<void> {\n // Get the latest version of the template\n const templateVersion = await extractPackageJsonVersionFromUri(TEMPLATE_URL);\n\n // Display CLI welcome banner\n showWelcome(templateVersion);\n\n // Shared theme to clear prompts after answering\n const promptTheme = { prefix: '', style: { answer: (text: string) => text } };\n const promptContext = { clearPromptOnDone: true };\n\n // Choose which template snapshot to scaffold from: a stable release or the\n // latest commit. Skipped when a custom --template is provided (ref doesn't apply).\n const templateRef = cli.options.template ? undefined : await promptTemplateRef(promptTheme, promptContext);\n\n // Prompt for project name if not provided\n if (!cli.directory) {\n cli.directory = await input(\n {\n message: 'Enter your project name',\n default: 'my-cella-app',\n theme: promptTheme,\n validate: (name) => {\n const validation = validateProjectName(basename(resolve(name)));\n return validation.valid ? true : `Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`;\n },\n },\n promptContext,\n );\n }\n\n const targetFolder = resolve(cli.directory);\n const projectName = basename(targetFolder)?.toLowerCase() || 'project';\n\n // Check if the target folder exists and is not empty\n if (existsSync(targetFolder) && !(await isEmptyDirectory(targetFolder))) {\n const dirName = cli.directory === '.' ? 'Current directory' : `Target directory \"${targetFolder}\"`;\n const message = `${dirName} is not empty. Please choose how you would like to proceed:`;\n\n const action = await select(\n {\n message,\n theme: promptTheme,\n choices: [\n { name: 'Cancel and exit', value: 'cancel' },\n { name: 'Ignore existing files and continue', value: 'ignore' },\n ],\n },\n promptContext,\n );\n\n if (action === 'cancel') {\n process.exit(1);\n }\n }\n\n // Scan sibling directories and prompt for port offset\n const portOffset = cli.options.portOffset ?? (await promptPortOffset(targetFolder, promptTheme, promptContext));\n confirmChoice('Port offset', formatOffset(portOffset));\n\n // Prompt for admin email. Default uses the reserved `example.com` domain (RFC 2606) so the\n // seed address can never resolve or route real mail — it's a placeholder, not a real inbox.\n const adminEmail =\n cli.options.adminEmail ??\n (await input(\n {\n message: 'Admin email for initial seed user',\n default: 'admin@example.com',\n theme: promptTheme,\n },\n promptContext,\n ));\n confirmChoice('Admin email', adminEmail);\n\n // Proceed with the project creation\n const createOptions: CreateOptions = {\n projectName,\n targetFolder,\n packageManager: cli.packageManager,\n templateUrl: cli.options.template,\n templateRef,\n portOffset,\n adminEmail,\n skipInstall: cli.options.skipInstall,\n };\n\n await create(createOptions);\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n\n/**\n * Show a short intro with the two starting points (latest release and latest\n * commit, each with its date) and let the user choose which template snapshot to\n * scaffold from. Returns the giget ref (release tag or commit SHA), or undefined\n * to use the default branch (when metadata is unavailable).\n */\nasync function promptTemplateRef(theme: object, context: object): Promise<string | undefined> {\n const [release, commit] = await Promise.all([fetchLatestRelease(TEMPLATE_URL), fetchLatestCommit(TEMPLATE_URL)]);\n\n // No metadata (offline / rate-limited) — fall back to the default branch silently.\n if (!release && !commit) return undefined;\n\n // One-sentence intro followed by the two data points.\n console.info(pc.dim('You are about to install the Cella template.'));\n console.info();\n\n if (release) console.info(` Latest release: ${release.tag} ${pc.dim(`· ${release.date}`)}`);\n if (commit) {\n console.info(` Latest commit: ${commit.shortSha} ${pc.dim(`· ${commit.message} · ${commit.date}`)}`);\n // Clickable link to the exact commit on GitHub, in grey below the summary line.\n const repo = TEMPLATE_URL.replace('github:', '');\n console.info(` ${pc.gray(`https://github.com/${repo}/commit/${commit.sha}`)}`);\n }\n console.info();\n\n // Only one available — use it without prompting.\n if (!release) return commit?.sha;\n if (!commit) return release.tag;\n\n const choice = await select(\n {\n message: 'Start from',\n theme,\n choices: [\n { name: 'Latest release', value: 'release' },\n { name: 'Latest commit', value: 'commit' },\n ],\n },\n context,\n );\n\n // The prompt clears itself on done, so keep the choice visible with a checkmark.\n if (choice === 'release') {\n confirmChoice('Latest release', release.tag);\n return release.tag;\n }\n confirmChoice('Latest commit', commit.shortSha);\n return commit.sha;\n}\n\n/** Format an offset as a port overview string, e.g. \"10 → :3010 / :4010 / :5442\" */\nfunction formatOffset(o: number, suffix = ''): string {\n return `${o} → :${3000 + o} / :${4000 + o} / :${5432 + o}${suffix}`;\n}\n\n/** Scan sibling forks and prompt the user to pick a port offset */\nasync function promptPortOffset(targetFolder: string, theme: object, context: object): Promise<number> {\n const usedPorts = await detectUsedPorts(targetFolder);\n const suggested = findNextOffset(usedPorts);\n\n // Fold a compact hint into the prompt message itself so it's wiped by clearPromptOnDone\n // once an offset is chosen — no leftover \"Detected forks\" trail. Override the default\n // message style (which bolds everything) so only the \"Port offset\" title is bold.\n let message = `${pc.bold('Port offset')} ${pc.dim('· to avoid conflicts with other forks/clones')}`;\n if (usedPorts.length > 0) {\n const count = usedPorts.length;\n message += pc.dim(`\\nDetected ${count} sibling fork${count === 1 ? '' : 's'} · suggested offset ${suggested}`);\n }\n const portTheme = {\n ...(theme as Record<string, unknown>),\n style: { ...((theme as { style?: object }).style ?? {}), message: (text: string) => text },\n };\n\n // Presets exclude 0 (always offered as the explicit default below) and the suggested\n // value (offered first) to avoid duplicate choices.\n const presets = [10, 20, 30].filter((o) => o !== suggested);\n const choice = await select(\n {\n message,\n theme: portTheme,\n choices: [\n ...(suggested > 0 ? [{ name: formatOffset(suggested, ' (suggested)'), value: suggested }] : []),\n { name: formatOffset(0, ' (default)'), value: 0 },\n ...presets.map((o) => ({ name: formatOffset(o), value: o })),\n { name: 'Custom offset', value: -1 },\n ],\n },\n context,\n );\n\n if (choice !== -1) return choice;\n\n const custom = await input(\n {\n message: 'Enter custom offset (0-490, multiples of 10)',\n default: String(suggested),\n theme,\n validate: (val) => {\n const n = Number(val);\n if (Number.isNaN(n) || n < 0 || n > 490) return 'Must be between 0 and 490';\n if (n % 10 !== 0) return 'Must be a multiple of 10';\n return true;\n },\n },\n context,\n );\n return Number(custom);\n}\n","import pc from 'picocolors';\nimport packageJson from '../package.json' with { type: 'json' };\n\n/** Name of this CLI tool */\nexport const NAME = 'create cella';\n\n/** Thin line divider for console output (60 chars wide) */\nexport const DIVIDER = '─'.repeat(60);\n\n/** URL of the template repository */\nexport const TEMPLATE_URL = 'github:cellajs/cella';\n\n/** URL to the repository */\nexport const CELLA_REMOTE_URL = 'git@github.com:cellajs/cella.git';\n\n/** Export details from package.json */\nexport const DESCRIPTION: string = packageJson.description;\nexport const VERSION: string = packageJson.version;\nexport const WEBSITE: string = packageJson.homepage;\n\nexport function getHeaderLine(templateVersion?: string): string {\n const leftText = `⧈ ${NAME} · ${VERSION} · cella ${templateVersion}`;\n const left = `${pc.cyan(`⧈ ${NAME}`)} ${pc.dim(`· ${VERSION} · cella ${templateVersion}`)}`;\n const right = pc.cyan(WEBSITE);\n const padding = Math.max(1, 60 - leftText.length - WEBSITE.length);\n return `${left}${' '.repeat(padding)}${right}`;\n}\n\n// Files or folders to be removed from the template after downloading\nexport const TO_REMOVE: string[] = ['./cli/create', './info/QUICKSTART.md'];\n\n// Specific folder contents to be cleaned out from the template\nexport const TO_CLEAN: string[] = ['./backend/drizzle'];\n\n// Files to copy/paste after downloading\nexport const TO_COPY: Record<string, string> = {\n './frontend/.env.example': './frontend/.env',\n './info/QUICKSTART.md': 'README.md',\n};\n\n/**\n * Placeholder config template that replaces `shared/config/config.default.ts` in new forks.\n * Ships inside the create-cella package (see package.json `files`) and is resolved relative\n * to the package — NOT the downloaded fork. Contains `__project_name__` and `__project_slug__`\n * tokens interpolated at create time.\n */\nexport const PLACEHOLDER_CONFIG = 'configs/default-config.ts.template';\n\n/**\n * Read a `.env.example` file and apply key=value replacements.\n * Comments and unmatched keys are preserved as-is.\n * Returns null if the file doesn't exist (caller should generate from scratch).\n */\nexport async function generateEnvFromExample(\n examplePath: string,\n replacements: Record<string, string>,\n): Promise<string | null> {\n const { readFile } = await import('node:fs/promises');\n let content: string;\n try {\n content = await readFile(examplePath, 'utf8');\n } catch {\n return null;\n }\n\n return content.replace(/^([A-Z_][A-Z0-9_]*)=(.*)$/gm, (match, key, _value) => {\n if (key in replacements) return `${key}=${replacements[key]}`;\n return match;\n });\n}\n\n/**\n * Replacement map for `backend/.env`.\n * The backend `.env` is the single source of truth for the project slug, database\n * ports (consumed by `backend/compose.yaml`), connection URLs, admin email and API port.\n */\nexport function getBackendEnvReplacements(\n slug: string,\n adminEmail: string,\n portOffset: number,\n): Record<string, string> {\n const db = 5432 + portOffset;\n return {\n PROJECT_SLUG: slug,\n DB_PORT: String(db),\n DB_TEST_PORT: String(5434 + portOffset),\n DATABASE_URL: `postgres://runtime_role:dev_password@0.0.0.0:${db}/postgres`,\n DATABASE_ADMIN_URL: `postgres://postgres:postgres@0.0.0.0:${db}/postgres`,\n DATABASE_CDC_URL: `postgres://admin_role:dev_password@0.0.0.0:${db}/postgres`,\n ADMIN_EMAIL: adminEmail,\n PORT: String(4000 + portOffset),\n };\n}\n\n/**\n * Generate env config files with project-specific values.\n * All configs go through the same data-driven loop.\n * Values prefixed with '=' are emitted as raw TS expressions (not quoted).\n */\nexport function generateEnvConfigs(slug: string, name: string, portOffset: number): Record<string, string> {\n const fe = 3000 + portOffset;\n const be = 4000 + portOffset;\n\n const header =\n \"import type { DeepPartial } from '../src/config-builder/types';\\nimport type _default from './config.default';\\n\";\n\n // Per-environment specs: optional imports + object props (= prefix → raw TS expression)\n const envs: Record<string, { imports?: string; props: Record<string, string | boolean> }> = {\n development: {\n props: {\n slug: `${slug}-development`,\n domain: '',\n frontendUrl: `http://localhost:${fe}`,\n backendUrl: `http://localhost:${be}`,\n backendAuthUrl: `http://localhost:${be}/auth`,\n },\n },\n staging: {\n props: {\n slug: `${slug}-staging`,\n domain: `${slug}.example.com`,\n frontendUrl: `https://staging.${slug}.example.com`,\n backendUrl: `https://api-staging.${slug}.example.com`,\n backendAuthUrl: `https://api-staging.${slug}.example.com/auth`,\n },\n },\n tunnel: {\n props: {\n frontendUrl: `https://localhost:${fe}`,\n backendUrl: `https://${slug}.ngrok.dev`,\n backendAuthUrl: `https://${slug}.ngrok.dev/auth`,\n },\n },\n test: {\n imports: \"import development from './config.development';\\n\",\n props: {\n domain: '',\n frontendUrl: '=development.frontendUrl',\n backendUrl: '=development.backendUrl',\n backendAuthUrl: '=development.backendAuthUrl',\n },\n },\n production: { props: { maintenance: false } },\n };\n\n // Serialize value: '=' prefix → raw TS expression, boolean → literal, string → quoted\n const lit = (v: string | boolean) => {\n if (typeof v === 'boolean') return String(v);\n if (v.startsWith('=')) return v.slice(1);\n return `'${v}'`;\n };\n\n const result: Record<string, string> = {};\n\n for (const [mode, { imports = '', props }] of Object.entries(envs)) {\n const nameEntry = mode !== 'production' ? ` name: '${name} ${mode.toUpperCase()}',\\n` : '';\n const body = Object.entries(props)\n .map(([k, v]) => ` ${k}: ${lit(v)},`)\n .join('\\n');\n result[`./shared/config/config.${mode}.ts`] =\n `${imports}${header}\\nexport default {\\n mode: '${mode}',\\n${nameEntry}${body}\\n} satisfies DeepPartial<typeof _default>;\\n`;\n }\n\n return result;\n}\n","{\n \"name\": \"@cellajs/create-cella\",\n \"version\": \"0.2.3\",\n \"private\": false,\n \"license\": \"MIT\",\n \"description\": \"Cella is a TypeScript template to create web apps with sync and offline capabilities.\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/cellajs/cella\",\n \"directory\": \"cli/create-cella\"\n },\n \"homepage\": \"https://cellajs.com\",\n \"author\": \"CellaJS <info@cellajs.com>\",\n \"engines\": {\n \"node\": \"24.x\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"configs\",\n \"dist\",\n \"index.js\",\n \"README.md\"\n ],\n \"imports\": {\n \"#/*\": \"./src/*\"\n },\n \"bin\": {\n \"create-cella\": \"index.js\"\n },\n \"scripts\": {\n \"start\": \"tsx ./src/create-cella-cli.ts\",\n \"try\": \"tsx ./src/create-cella-cli.ts ../../../cella-try-app --template ../.. --skip-install\",\n \"clean\": \"rimraf ./dist\",\n \"build\": \"tsup\",\n \"test-build\": \"pnpm run build && node index.js\",\n \"prepublishOnly\": \"pnpm run test:release\",\n \"check\": \"pnpm ts && pnpm biome check --write .\",\n \"ts\": \"tsgo --pretty\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"test\": \"vitest run\",\n \"test:release\": \"pnpm run build && vitest run tests/release-smoke.test.ts\",\n \"test:watch\": \"vitest\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^8.5.2\",\n \"commander\": \"^15.0.0\",\n \"giget\": \"^3.3.0\",\n \"nano-spawn\": \"^2.0.0\",\n \"ora\": \"^9.4.1\",\n \"picocolors\": \"^1.1.1\",\n \"validate-npm-package-name\": \"^8.0.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"24.10.13\",\n \"@types/validate-npm-package-name\": \"^4.0.2\",\n \"@typescript/native-preview\": \"7.0.0-dev.20260628.1\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.22.4\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.9\"\n }\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { cp, mkdir, rm } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { checkbox } from '@inquirer/prompts';\nimport { downloadTemplate } from 'giget';\nimport pc from 'picocolors';\nimport { addRemote } from '#/add-remote';\nimport { TEMPLATE_URL } from '#/constants';\nimport { type CreateOptions, confirmChoice, showSuccess } from '#/modules/cli';\nimport { cleanTemplate } from '#/utils/clean-template';\nimport { gitAddAll, gitCommit, gitInit } from '#/utils/git';\nimport { createProgress, pauseSpinner, resumeSpinner } from '#/utils/progress';\nimport { generate, install } from '#/utils/run-package-manager-command';\nimport { optionalModuleFolders, scanOptionalModules } from '#/utils/scan-optional-modules';\n\n/** Check if a path is a local directory */\nfunction isLocalPath(path: string): boolean {\n return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');\n}\n\nfunction shouldSkipStep(name: 'install' | 'generate' | 'git' | 'remote'): boolean {\n return process.env[`CREATE_CELLA_SKIP_${name.toUpperCase()}`] === 'true';\n}\n\nexport async function create({\n projectName,\n targetFolder,\n packageManager,\n templateUrl,\n templateRef,\n portOffset,\n adminEmail,\n skipInstall = false,\n silent = false,\n}: CreateOptions): Promise<void> {\n // Save the original working directory\n const originalCwd = process.cwd();\n\n // Use custom template or default\n const template = templateUrl || TEMPLATE_URL;\n const isLocalTemplate = templateUrl && isLocalPath(templateUrl);\n\n const progress = createProgress('creating project', silent);\n\n try {\n // Create the target folder\n progress.step('creating project folder');\n await mkdir(targetFolder, { recursive: true });\n process.chdir(targetFolder);\n\n // Download or copy the template\n if (isLocalTemplate) {\n progress.step('copying local template');\n const sourcePath = resolve(originalCwd, templateUrl);\n\n // Check if target is inside source (would cause EINVAL). Use a path-boundary check\n // (not a string prefix) so siblings like `cella-try-app` next to `cella` aren't\n // misdetected as being inside the template.\n const rel = relative(sourcePath, targetFolder);\n const targetInsideTemplate = rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n if (targetInsideTemplate) {\n throw new Error(\n 'Cannot create project inside the template directory.\\n' +\n ` Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`,\n );\n }\n\n // Copy only what git would track (respects .gitignore: no build output, no local .env secrets).\n // Falls back to a plain recursive copy when the template isn't a git repo.\n let tracked: string[] = [];\n try {\n tracked = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {\n cwd: sourcePath,\n })\n .toString()\n .split('\\0')\n .filter(Boolean);\n } catch {\n // not a git repo - fall through to recursive copy\n }\n\n if (tracked.length > 0) {\n for (const rel of tracked) {\n const src = join(sourcePath, rel);\n if (!existsSync(src)) continue; // tracked but deleted in the working tree\n const dest = join(targetFolder, rel);\n await mkdir(dirname(dest), { recursive: true });\n await cp(src, dest);\n }\n } else {\n await cp(sourcePath, targetFolder, {\n recursive: true,\n filter: (src) => !src.includes('node_modules') && !src.includes('.git'),\n });\n }\n } else {\n // Pin to the chosen ref (release tag or commit SHA) when provided.\n const downloadId = templateRef ? `${template}#${templateRef}` : template;\n progress.step(`downloading cella template${templateRef ? ` (${templateRef})` : ''}`);\n await downloadTemplate(downloadId, {\n cwd: process.cwd(),\n dir: targetFolder,\n force: true,\n provider: 'github',\n });\n }\n\n // Ask which optional modules to keep, then drop deselected paths\n if (!silent) await promptOptionalModules(targetFolder);\n\n // Clean the template, generate configs\n progress.step('cleaning template');\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });\n\n // Install dependencies (generate needs installed deps, so --skip-install skips both)\n if (!skipInstall && !shouldSkipStep('install')) {\n progress.step('installing dependencies');\n await install(packageManager);\n }\n\n // Generate SQL files\n if (!skipInstall && !shouldSkipStep('generate')) {\n progress.step('generating migrations');\n await generate(packageManager);\n }\n\n // Initialize git repository\n const gitFolderPath = join(targetFolder, '.git');\n if (!shouldSkipStep('git') && !existsSync(gitFolderPath)) {\n progress.step('initializing git');\n await gitInit(targetFolder);\n await gitAddAll(targetFolder);\n await gitCommit(targetFolder, 'Initial commit');\n }\n\n // Add upstream remote\n if (!shouldSkipStep('remote')) {\n progress.step('adding upstream remote');\n await addRemote({ targetFolder, silent: true });\n }\n\n // Done\n progress.done(pc.bold(`created ${projectName}`));\n } catch (error) {\n progress.fail(error instanceof Error ? error.message : String(error));\n // Rethrow so callers (CLI entry, tests) see the real failure. The CLI entry\n // turns this into a non-zero exit; swallowing it here with process.exit hid\n // the underlying error from CI and vitest.\n throw error;\n }\n\n // Check if the working directory needs to be changed\n const needsCd = originalCwd !== targetFolder;\n const relativePath = relative(originalCwd, targetFolder);\n\n // Display final success message\n showSuccess(projectName, targetFolder, relativePath, needsCd, packageManager);\n}\n\n/**\n * Scan the downloaded template for optional modules and let the user deselect them.\n * Deselected modules have their whole folder removed before cleaning. Default keeps\n * everything (parity with previous behavior).\n */\nasync function promptOptionalModules(targetFolder: string): Promise<void> {\n const modules = await scanOptionalModules(targetFolder);\n if (modules.length === 0) return;\n\n // Non-interactive (CI, piped stdin, no TTY): can't render a checkbox — keep every module\n // rather than crashing with \"User force closed the prompt\". Matches the default-keep behavior.\n if (!process.stdin.isTTY) return;\n\n pauseSpinner();\n // Blank line to separate the preceding progress step from this prompt.\n console.info();\n const keep = await checkbox(\n {\n message: 'Optional modules to include',\n // Leading space on the radio icons adds a gap after the cursor (inquirer renders\n // `${cursor}${checkbox} ${name}` with no space between cursor and checkbox).\n theme: { icon: { checked: ` ${pc.green('◉')}`, unchecked: ' ◯' } },\n choices: modules.map((m) => ({\n name: `${m.name} ${pc.dim(`· ${m.description}`)}`,\n value: m.name,\n checked: true,\n })),\n },\n // Clear the answered prompt so we can print a compact, non-bold summary that lists\n // only the kept module names (not their descriptions).\n { clearPromptOnDone: true },\n );\n\n // Keep the choice visible after the prompt clears: plain label plus the selected\n // module names only (not their descriptions), or \"none\" when everything was deselected.\n confirmChoice('Optional modules', keep.length ? keep.join(', ') : pc.dim('none'));\n resumeSpinner();\n\n // Remove every folder owned by a deselected module. `optionalModuleFolders` lists more\n // route variants than any single module uses; `force: true` ignores the ones absent here.\n const folders = modules.filter((m) => !keep.includes(m.name)).flatMap(optionalModuleFolders);\n await Promise.all(folders.map((f) => rm(join(targetFolder, f), { recursive: true, force: true })));\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Executes a Git command in a specific repository path and returns the trimmed stdout.\n * Uses execFile for safety (no shell injection) aligned with sync package pattern.\n *\n * @param args - The Git command arguments to execute (e.g., ['status'], ['checkout', 'branch-name']).\n * @param repoPath - The path to the Git repository.\n * @param options - Optional settings for command execution.\n * @returns The stdout of the Git command, trimmed of leading and trailing whitespace.\n */\nexport async function runGitCommand(\n args: string[],\n repoPath: string,\n options: { skipEditor?: boolean; maxBuffer?: number } = {},\n): Promise<string> {\n const gitArgs = repoPath ? ['-C', repoPath, ...args] : args;\n\n const env = {\n ...process.env,\n ...(options.skipEditor ? { GIT_EDITOR: 'true' } : {}),\n };\n\n // Default to 10MB buffer for typical outputs\n const maxBuffer = options.maxBuffer ?? 10 * 1024 * 1024;\n\n const { stdout } = await execFileAsync('git', gitArgs, { env, maxBuffer });\n\n return stdout.trim();\n}\n\n/**\n * Initializes a new Git repository in the specified directory.\n */\nexport async function gitInit(repoPath: string): Promise<string> {\n return runGitCommand(['init'], repoPath);\n}\n\n/**\n * Stages all files in the repository.\n */\nexport async function gitAddAll(repoPath: string): Promise<string> {\n return runGitCommand(['add', '.'], repoPath);\n}\n\n/**\n * Creates a commit with the specified message.\n */\nexport async function gitCommit(repoPath: string, message: string): Promise<string> {\n return runGitCommand(['commit', '--quiet', '--no-verify', '-m', message], repoPath);\n}\n\n/**\n * Gets the URL of a remote.\n */\nexport async function gitRemoteGetUrl(repoPath: string, remoteName: string): Promise<string> {\n return runGitCommand(['remote', 'get-url', remoteName], repoPath);\n}\n\n/**\n * Adds a new remote.\n */\nexport async function gitRemoteAdd(repoPath: string, remoteName: string, remoteUrl: string): Promise<string> {\n return runGitCommand(['remote', 'add', remoteName, remoteUrl], repoPath);\n}\n\n/**\n * Removes a remote.\n */\nexport async function gitRemoteRemove(repoPath: string, remoteName: string): Promise<string> {\n return runGitCommand(['remote', 'remove', remoteName], repoPath);\n}\n","import { CELLA_REMOTE_URL } from '#/constants';\nimport type { AddRemoteOptions } from '#/modules/cli';\nimport { gitRemoteAdd, gitRemoteGetUrl, gitRemoteRemove } from '#/utils/git';\n\n/**\n * Adds or updates the upstream remote for the Cella template.\n * @param options - Configuration options\n * @param options.silent - If true, don't throw on failure (used when called from progress tracker)\n */\nexport async function addRemote({\n targetFolder,\n remoteUrl = CELLA_REMOTE_URL,\n remoteName = 'upstream',\n silent = false,\n}: AddRemoteOptions): Promise<void> {\n try {\n // Check if the remote exists\n let remote: string | null = null;\n\n try {\n remote = await gitRemoteGetUrl(targetFolder, remoteName);\n } catch {\n // If the remote doesn't exist, it throws a fatal error\n remote = null;\n }\n\n // Add or update the remote if it doesn't exist or differs from `remoteUrl`\n if (!remote) {\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n } else if (remote !== remoteUrl) {\n // Remove existing remote and set the new URL\n await gitRemoteRemove(targetFolder, remoteName);\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n }\n } catch (error) {\n if (!silent) {\n throw error;\n }\n }\n}\n","import { basename, resolve } from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { NAME, VERSION } from '#/constants';\nimport { validateProjectName } from '#/utils/validate-project-name';\nimport type { CLIConfig, CLIOptions } from './types';\n\nfunction parsePortOffset(value: string): number {\n const parsed = Number(value);\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 490) {\n throw new InvalidArgumentError('Port offset must be an integer between 0 and 490');\n }\n\n if (parsed % 10 !== 0) {\n throw new InvalidArgumentError('Port offset must be a multiple of 10');\n }\n\n return parsed;\n}\n\n// Initialize CLI variables\nlet directory: string | null = null;\nconst packageManager = 'pnpm';\n\n/**\n * Defines the root CLI command using Commander.\n * This command accepts CLI options and validates user input.\n */\nexport const command = new Command(NAME)\n .version(VERSION, '-v, --version', `output the current version of ${NAME}`)\n .argument('[directory]', 'the directory name for the new project')\n .usage('[directory] [options]')\n .helpOption('-h, --help', 'display this help message')\n .option('--template <path>', 'use a custom template (local path or github:user/repo)')\n .option('--port-offset <number>', 'set the port offset (0-490 in steps of 10)', parsePortOffset)\n .option('--admin-email <email>', 'set the admin email for the initial seed user')\n .option('--skip-install', 'scaffold only: skip dependency installation and migration generation')\n .action((name: string) => {\n const trimmedName = typeof name === 'string' ? name.trim() : name;\n\n if (trimmedName) {\n const validation = validateProjectName(basename(resolve(trimmedName)));\n\n if (!validation.valid) {\n throw new InvalidArgumentError(`Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`);\n }\n\n directory = trimmedName;\n }\n })\n .parse();\n\n// Gather the CLI options and arguments\nconst options: CLIOptions = command.opts<CLIOptions>();\n\n/**\n * Runs the CLI and returns the parsed configuration.\n * This function parses command line arguments and returns the CLI config.\n */\nexport function runCli(): CLIConfig {\n return {\n options,\n args: command.args,\n directory,\n packageManager,\n };\n}\n\n// Export CLI configuration for direct import\nexport const cli: CLIConfig = runCli();\n","import validate from 'validate-npm-package-name';\n\ninterface ValidationResult {\n valid: boolean;\n problems?: string[];\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n const nameValidation = validate(name);\n\n if (nameValidation.validForNewPackages) {\n return { valid: true };\n }\n\n return {\n valid: false,\n problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])],\n };\n}\n","import pc from 'picocolors';\nimport { DESCRIPTION, DIVIDER, getHeaderLine } from '#/constants';\n\n/** ASCII art logo for the CLI welcome screen. */\nfunction showAscii(): void {\n console.info(pc.cyan(' _ _ '));\n console.info(pc.cyan('▒▓█████▓▒ ___ ___| | | __ _ '));\n console.info(pc.cyan('▒▓█ █▓▒ / __/ _ \\\\ | |/ _` | '));\n console.info(pc.cyan('▒▓█ █▓▒ | (_| __/ | | (_| | '));\n console.info(pc.cyan('▒▓█████▓▒ \\\\___\\\\___|_|_|\\\\__,_| '));\n}\n\n/**\n * Displays the compact CLI welcome header.\n * @param templateVersion - The version of the cella template being used\n */\nexport function showWelcome(templateVersion: string): void {\n console.info();\n showAscii();\n console.info();\n console.info(pc.dim(DESCRIPTION));\n console.info();\n console.info(getHeaderLine(templateVersion));\n console.info(DIVIDER);\n}\n\n/**\n * Print a cleared prompt's outcome as a persistent confirmation line:\n * `✓ <label> · <value>`, followed by a blank separator line. Used after prompts\n * that clear themselves on done so the chosen value stays visible.\n */\nexport function confirmChoice(label: string, value: string): void {\n console.info(`${pc.green('✓')} ${label} · ${value}`);\n console.info();\n}\n\n/**\n * Displays the final success message after project creation.\n */\nexport function showSuccess(\n projectName: string,\n _targetFolder: string,\n relativePath: string,\n needsCd: boolean,\n packageManager: string,\n): void {\n console.info();\n\n console.info(pc.bold('Next steps'));\n console.info(DIVIDER);\n console.info();\n\n if (needsCd) {\n console.info(`${pc.dim('$')} cd ${pc.cyan(relativePath)}`);\n }\n console.info(\n `${pc.dim('$')} ${pc.cyan(`${packageManager} docker`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} seed`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} dev`)}`,\n );\n console.info();\n\n // Point to the generated README for first-run details and sign-in credentials. Use the\n // path relative to the new project so the terminal linkifies the right file (not this CLI's).\n const readmePath = relativePath ? `${relativePath}/README.md` : 'README.md';\n console.info(`${pc.gray('To get started building, please read:')} ${pc.cyan(readmePath)}`);\n console.info();\n console.info(`🥳 enjoy building ${pc.green(projectName)} with cella!`);\n console.info();\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\nimport {\n generateEnvConfigs,\n generateEnvFromExample,\n getBackendEnvReplacements,\n PLACEHOLDER_CONFIG,\n TO_CLEAN,\n TO_COPY,\n TO_REMOVE,\n} from '#/constants';\n\nconst warningMark = pc.yellow('⚠');\n\n/**\n * Resolve the placeholder config template that ships inside the create-cella package.\n *\n * The template is the CLI's own asset (listed in package.json `files`), NOT part of the\n * downloaded fork. It must therefore be read relative to this module, which lives at:\n * - dev (tsx): `<pkg>/src/utils/clean-template.ts` → template at `../../<PLACEHOLDER_CONFIG>`\n * - bundled: `<pkg>/dist/index.js` → template at `../<PLACEHOLDER_CONFIG>`\n */\nasync function resolvePlaceholderConfigPath(): Promise<string> {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [path.resolve(here, '../..', PLACEHOLDER_CONFIG), path.resolve(here, '..', PLACEHOLDER_CONFIG)];\n for (const candidate of candidates) {\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n // try next candidate\n }\n }\n // Fall back to the first candidate so the caller surfaces a clear ENOENT.\n return candidates[0];\n}\n\n/**\n * Cleans the specified template by removing designated folders and files.\n * @param params - Parameters containing the target folder, project name, and optional extra edits.\n */\nexport async function cleanTemplate({\n targetFolder,\n projectName,\n displayName,\n portOffset = 0,\n adminEmail = `admin@${projectName}.example.com`,\n}: {\n targetFolder: string;\n projectName: string;\n displayName: string;\n portOffset?: number;\n adminEmail?: string;\n}): Promise<void> {\n // Change the current working directory to targetFolder if not already set\n if (process.cwd() !== targetFolder) {\n process.chdir(targetFolder);\n }\n\n return new Promise<void>((resolve, reject) => {\n (async () => {\n try {\n // Copy specified files\n for (const [src, dest] of Object.entries(TO_COPY)) {\n const srcAbsolutePath = path.resolve(targetFolder, src);\n const destAbsolutePath = path.resolve(targetFolder, dest);\n await copyFile(srcAbsolutePath, destAbsolutePath);\n }\n\n // Replace config.default.ts with interpolated placeholder config\n await applyPlaceholderConfig(targetFolder, projectName);\n\n // Generate backend .env from backend/.env.example.\n // The backend .env is the single source of truth for the project slug and DB ports\n // (consumed by backend/compose.yaml). There is no root .env.\n const backendReplacements = getBackendEnvReplacements(projectName, adminEmail, portOffset);\n const backendEnv = await generateEnvFromExample(\n path.resolve(targetFolder, 'backend/.env.example'),\n backendReplacements,\n );\n if (backendEnv) {\n await fs.writeFile(path.resolve(targetFolder, 'backend/.env'), backendEnv, 'utf8');\n }\n\n // Generate minimal env config files with project values and ports baked in\n const envConfigs = generateEnvConfigs(projectName, displayName, portOffset);\n await Promise.all(\n Object.entries(envConfigs).map(([filePath, content]) =>\n fs.writeFile(path.resolve(targetFolder, filePath), content, 'utf8'),\n ),\n );\n\n // Clean specified folder contents\n await Promise.all(\n TO_CLEAN.map((folderPath) => {\n const absolutePath = path.resolve(targetFolder, folderPath);\n return removeFolderContents(absolutePath);\n }),\n );\n\n // Remove specified files and folders\n await Promise.all(\n TO_REMOVE.map((filePath) => {\n const absolutePath = path.resolve(targetFolder, filePath);\n return removeFileOrFolder(absolutePath);\n }),\n );\n\n resolve();\n } catch (err) {\n reject(`Error during the cleaning process: ${err}`);\n }\n })();\n });\n}\n\n/**\n * Removes all contents within a specified folder.\n * @param folderPath - The path of the folder to clean.\n */\nasync function removeFolderContents(folderPath: string): Promise<void> {\n // List all files in the folder\n const files = await fs.readdir(folderPath);\n\n await Promise.all(\n files.map(async (file) => {\n const filePath = path.join(folderPath, file);\n\n // Get the file or folder statistics\n const stat = await fs.lstat(filePath);\n\n // If it's a directory, remove it and all its contents\n if (stat.isDirectory()) {\n await fs.rm(filePath, { recursive: true, force: true });\n } else {\n // If it's a file, remove it\n await fs.rm(filePath);\n }\n }),\n );\n}\n\n/**\n * Removes a specified file or folder.\n * @param pathToRemove - The path to the file or folder to remove.\n */\nasync function removeFileOrFolder(pathToRemove: string): Promise<void> {\n await fs.rm(pathToRemove, { recursive: true, force: true });\n}\n\n/**\n * Helper function to copy files if the source exists.\n * @param src - The source file path.\n * @param dest - The destination file path.\n */\nasync function copyFile(src: string, dest: string): Promise<void> {\n try {\n // Check if the source file exists\n await fs.access(src);\n\n // Ensure the destination directory exists\n await fs.mkdir(path.dirname(dest), { recursive: true });\n\n // Copy the file\n await fs.copyFile(src, dest);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Source file \"${src}\" does not exist > Skip copy`);\n } else {\n throw err;\n }\n }\n}\n\n/**\n * Read the placeholder config template, interpolate project tokens, and\n * write it as `shared/config/config.default.ts` — replacing the original.\n */\nasync function applyPlaceholderConfig(targetFolder: string, projectName: string): Promise<void> {\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n\n const src = await resolvePlaceholderConfigPath();\n const dest = path.resolve(targetFolder, './shared/config/config.default.ts');\n\n try {\n let content = await fs.readFile(src, 'utf8');\n content = content.replaceAll('__project_name__', displayName);\n content = content.replaceAll('__project_slug__', projectName);\n await fs.writeFile(dest, content, 'utf8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Placeholder config \"${src}\" not found > Skip`);\n } else {\n throw err;\n }\n }\n}\n","/**\n * Progress Tracker Utility\n *\n * Provides compact progress tracking for CLI operations.\n * Uses a single spinner that updates in place.\n */\n\nimport ora, { type Ora } from 'ora';\nimport pc from 'picocolors';\n\n/** Global reference to the currently active spinner */\nlet activeSpinner: Ora | null = null;\n\n/** Pause the active spinner (for interactive prompts) */\nexport function pauseSpinner(): void {\n if (activeSpinner) {\n activeSpinner.stop();\n }\n}\n\n/** Resume the active spinner after a prompt */\nexport function resumeSpinner(): void {\n if (activeSpinner) {\n activeSpinner.start();\n }\n}\n\nexport interface ProgressTracker {\n /** Update the current step (shown as spinner text) */\n step: (message: string) => void;\n /** Mark the tracker as complete with final message */\n done: (message: string) => void;\n /** Mark the tracker as failed with error message */\n fail: (message: string) => void;\n}\n\n/**\n * Creates a progress tracker for a multi-step operation.\n * Shows a single spinner that updates in place.\n *\n * @param title - The initial title for the spinner\n * @returns A ProgressTracker instance\n *\n * @example\n * const progress = createProgress('creating project');\n * progress.step('downloading template');\n * progress.step('installing dependencies');\n * progress.done('project created');\n */\nexport function createProgress(title: string, silent = false): ProgressTracker {\n const completedSteps: string[] = [];\n\n const spinner = ora({\n text: title,\n isSilent: silent,\n });\n\n activeSpinner = spinner;\n spinner.start();\n\n // Helper to log (respects silent mode)\n const log = (msg: string) => {\n if (!silent) console.info(msg);\n };\n\n return {\n step: (message: string) => {\n // Complete previous step with green check if there was one\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n }\n completedSteps.push(message);\n spinner.text = message;\n spinner.start();\n },\n\n done: (message: string) => {\n // Complete the last step with green check\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n } else {\n spinner.stop();\n }\n activeSpinner = null;\n if (message) {\n log('');\n log(`${pc.green('✓')} ${message}`);\n }\n },\n\n fail: (message: string) => {\n spinner.stop();\n activeSpinner = null;\n log(`${pc.red('✗')} ${pc.red(message)}`);\n },\n };\n}\n","import spawn from 'nano-spawn';\n\n/**\n * Executes a command using the specified package manager (e.g., pnpm).\n *\n * @param packageManager - The package manager to use (e.g., 'pnpm').\n * @param args - The arguments to pass to the package manager command.\n * @param env - Additional environment variables to set during command execution.\n * @returns A promise that resolves if the command executes successfully; otherwise, it rejects with an error message.\n */\nexport async function runPackageManagerCommand(\n packageManager: string,\n args: string[],\n env: Record<string, string> = {},\n): Promise<void> {\n try {\n await spawn(packageManager, args, {\n env: { ...process.env, ...env },\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n const err = error as { stdout?: string; stderr?: string };\n throw new Error(`\"${packageManager} ${args.join(' ')}\" failed ${err.stdout || ''} ${err.stderr || ''}`);\n }\n}\n\n/**\n * Installs dependencies using the specified package manager.\n *\n * @param packageManager - The package manager to use for installation (e.g., 'pnpm').\n * @returns A promise that resolves if the installation completes successfully; otherwise, it rejects with an error.\n */\nexport async function install(packageManager: string): Promise<void> {\n return runPackageManagerCommand(packageManager, ['install'], {\n NODE_ENV: 'development',\n });\n}\n\n/**\n * Generates SQL files using the specified package manager.\n *\n * @param packageManager - The package manager to use for generation (e.g., 'pnpm').\n * @returns A promise that resolves if the generation completes successfully; otherwise, it rejects with an error.\n */\nexport async function generate(packageManager: string): Promise<void> {\n return runPackageManagerCommand(packageManager, ['generate'], {\n NODE_ENV: 'development',\n });\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\n\n/** An optional module discovered in a `*-module.ts` registration. */\nexport interface OptionalModule {\n name: string;\n description: string;\n /** Module folder, relative to the project root, removed when deselected. */\n folder: string;\n}\n\n/** Directories never worth scanning. */\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage']);\n\n/**\n * Scan the template for `*-module.ts` files that register `optional: true` and return\n * their name, description and folder. Pure static regex parsing — no imports/eval — so it\n * runs before dependencies are installed. The folder is the module file's directory.\n */\nexport async function scanOptionalModules(targetFolder: string): Promise<OptionalModule[]> {\n const files: string[] = [];\n await collectModuleFiles(targetFolder, files);\n\n const modules: OptionalModule[] = [];\n for (const file of files) {\n const src = await readFile(file, 'utf8');\n if (!/optional:\\s*true/.test(src)) continue;\n\n const name = src.match(/name:\\s*['\"]([^'\"]+)['\"]/)?.[1];\n const description = src.match(/description:\\s*['\"]([^'\"]+)['\"]/)?.[1] ?? '';\n if (!name) continue;\n\n modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });\n }\n return modules.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Every project-root-relative folder removed when an optional module is deselected: its own\n * module folder plus the route and static-asset folders it may own. Route folders are\n * speculative — a module owns either a pathless `_<name>` (URL-transparent) or a path-based\n * `<name>` route, under `routes/_public` and/or `routes/_app` — so callers remove only the\n * variants that actually exist. Single-sourced here so removal and tests stay in sync.\n */\nexport function optionalModuleFolders({ name, folder }: OptionalModule): string[] {\n return [\n folder,\n `frontend/src/routes/_public/_${name}`,\n `frontend/src/routes/_public/${name}`,\n `frontend/src/routes/_app/_${name}`,\n `frontend/src/routes/_app/${name}`,\n `frontend/public/static/${name}`,\n ];\n}\n\nasync function collectModuleFiles(dir: string, out: string[]): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await collectModuleFiles(join(dir, entry.name), out);\n } else if (entry.name.endsWith('-module.ts')) {\n out.push(join(dir, entry.name));\n }\n }\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\ninterface UsedPorts {\n project: string;\n frontend: number;\n backend: number;\n offset: number;\n}\n\n/**\n * Scan sibling directories for existing cella forks and extract their dev ports.\n * Looks for `shared/config/config.development.ts` to identify cella-based projects.\n */\nexport async function detectUsedPorts(targetFolder: string): Promise<UsedPorts[]> {\n const parentDir = dirname(targetFolder);\n const used: UsedPorts[] = [];\n\n let siblings: string[];\n try {\n siblings = await readdir(parentDir);\n } catch {\n return used;\n }\n\n for (const name of siblings) {\n const configPath = join(parentDir, name, 'shared/config/config.development.ts');\n try {\n const content = await readFile(configPath, 'utf8');\n const feMatch = content.match(/frontendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n const beMatch = content.match(/backendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n if (feMatch && beMatch) {\n const frontend = Number(feMatch[1]);\n const backend = Number(beMatch[1]);\n used.push({\n project: name,\n frontend,\n backend,\n offset: frontend - 3000,\n });\n }\n } catch {\n // Not a cella fork, skip\n }\n }\n\n return used;\n}\n\n/** Find the next available offset (in steps of 10) that doesn't collide with existing forks. */\nexport function findNextOffset(usedPorts: UsedPorts[]): number {\n const usedOffsets = new Set(usedPorts.map((p) => p.offset));\n for (let offset = 0; offset <= 490; offset += 10) {\n if (!usedOffsets.has(offset)) return offset;\n }\n return 0;\n}\n","/**\n * Retrieves the version from the package.json file of a GitHub repository.\n * If the package.json file is not found or an error occurs, it returns 'unknown'.\n *\n * @param repositoryUrl {string} - The GitHub repository URL in the format 'github:user/repo'.\n * @param branch {string} - The branch name (defaults to 'main').\n * @returns {Promise<string>} - The version from the package.json file.\n */\nexport async function extractPackageJsonVersionFromUri(repositoryUrl: string, branch = 'main'): Promise<string> {\n // Extract owner and repo from the URL\n const [owner, repo] = repositoryUrl.replace('github:', '').split('/');\n\n // Use the GitHub contents API to fetch the package.json file for the given branch\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${branch}`;\n\n try {\n const response = await fetch(apiUrl, {\n headers: { Accept: 'application/vnd.github.raw+json' },\n });\n if (!response.ok) return 'unknown';\n\n const packageJson = (await response.json()) as { version?: string };\n\n // Return the version from the package.json, or 'unknown' if not found\n return packageJson.version || 'unknown';\n } catch {\n // If there's an error (file not found, etc.), return 'unknown'\n return 'unknown';\n }\n}\n","/**\n * Fetch lightweight metadata about the cella template from the GitHub API so the\n * scaffolder can offer \"latest release\" vs \"latest commit\" as a starting point.\n *\n * Both helpers fail soft (return null) on network/API errors or rate limits — the\n * caller falls back to the default branch when metadata is unavailable.\n */\n\n/** Parse 'github:owner/repo' (or 'owner/repo') into [owner, repo]. */\nfunction parseRepo(repositoryUrl: string): [string, string] {\n const [owner, repo] = repositoryUrl.replace('github:', '').split('/');\n return [owner, repo];\n}\n\n/** Format an ISO timestamp as a locale-independent YYYY-MM-DD date. */\nfunction toDate(iso?: string): string {\n return iso ? iso.slice(0, 10) : 'unknown';\n}\n\nconst GITHUB_HEADERS = { Accept: 'application/vnd.github+json' } as const;\n\nexport interface ReleaseInfo {\n /** Release tag, e.g. 'v0.5.0' */\n tag: string;\n /** Publication date, YYYY-MM-DD */\n date: string;\n}\n\nexport interface CommitInfo {\n /** Full commit SHA (used for a reproducible checkout) */\n sha: string;\n /** Short 7-char SHA for display */\n shortSha: string;\n /** First line of the commit message */\n message: string;\n /** Commit date, YYYY-MM-DD */\n date: string;\n}\n\n/**\n * Fetch the latest published template release (`v*` tag).\n *\n * cella publishes two release series — the template (`v*`) and the scaffolder\n * (`create-cella-v*`). We list releases (newest first) and pick the first\n * non-draft, non-prerelease `v*` release so the scaffolder version is ignored.\n */\nexport async function fetchLatestRelease(repositoryUrl: string): Promise<ReleaseInfo | null> {\n const [owner, repo] = parseRepo(repositoryUrl);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=30`;\n\n try {\n const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });\n if (!response.ok) return null;\n\n const releases = (await response.json()) as Array<{\n tag_name?: string;\n published_at?: string;\n draft?: boolean;\n prerelease?: boolean;\n }>;\n\n const release = releases.find((r) => !r.draft && !r.prerelease && /^v\\d/.test(r.tag_name ?? ''));\n if (!release?.tag_name) return null;\n\n return { tag: release.tag_name, date: toDate(release.published_at) };\n } catch {\n return null;\n }\n}\n\n/** Fetch the latest commit on a branch (defaults to 'main'). */\nexport async function fetchLatestCommit(repositoryUrl: string, branch = 'main'): Promise<CommitInfo | null> {\n const [owner, repo] = parseRepo(repositoryUrl);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`;\n\n try {\n const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });\n if (!response.ok) return null;\n\n const data = (await response.json()) as {\n sha?: string;\n commit?: { message?: string; author?: { date?: string } };\n };\n if (!data.sha) return null;\n\n const message = (data.commit?.message ?? '').split('\\n')[0];\n return { sha: data.sha, shortSha: data.sha.slice(0, 7), message, date: toDate(data.commit?.author?.date) };\n } catch {\n return null;\n }\n}\n","import { readdir } from 'node:fs/promises';\n\n/**\n * Checks if a directory is empty or only contains a .git directory.\n *\n * @param path - The path of the directory to check.\n * @returns Resolves to true if the directory is empty or contains only a .git folder, false otherwise.\n * @throws Throws an error if the path is not a directory or if there's an issue reading the directory.\n */\nexport async function isEmptyDirectory(path: string): Promise<boolean> {\n const files = await readdir(path);\n\n // Check if directory is empty or contains only the .git directory\n return files.length === 0 || (files.length === 1 && files[0] === '.git');\n}\n"],"mappings":"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","names":["existsSync","basename","resolve","pc","readFile","dirname","join","relative","resolve","pc","options","pc","pc","packageManager","pc","pc","resolve","pc","packageManager","path","packageManager","resolve","relative","rel","join","dirname","pc","readdir","readFile","dirname","join","readdir","path","basename","resolve","existsSync","pc"]}
1
+ {"version":3,"sources":["../src/create-cella-cli.ts","../src/constants.ts","../package.json","../src/create.ts","../src/utils/git/command.ts","../src/add-remote.ts","../src/modules/cli/commands.ts","../src/utils/validate-project-name.ts","../src/modules/cli/display.ts","../src/utils/clean-template.ts","../src/utils/download-template.ts","../src/utils/list-template-files.ts","../src/utils/progress.ts","../src/utils/scan-optional-modules.ts","../src/utils/detect-used-ports.ts","../src/utils/extract-package-json-version-from-uri.ts","../src/utils/fetch-template-metadata.ts","../src/utils/is-empty-directory.ts"],"sourcesContent":["#!/usr/bin/env tsx\n\nimport { existsSync } from 'node:fs';\nimport { basename, resolve } from 'node:path';\nimport { input, select } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { TEMPLATE_URL } from '#/constants';\nimport { create } from '#/create';\nimport { type CreateOptions, cli, confirmChoice, showWelcome } from '#/modules/cli';\nimport { detectUsedPorts, findNextOffset } from '#/utils/detect-used-ports';\nimport { extractPackageJsonVersionFromUri } from '#/utils/extract-package-json-version-from-uri';\nimport { fetchLatestCommit, fetchLatestRelease } from '#/utils/fetch-template-metadata';\nimport { isEmptyDirectory } from '#/utils/is-empty-directory';\nimport { validateProjectName } from '#/utils/validate-project-name';\n\nasync function main(): Promise<void> {\n // Get the latest version of the template\n const templateVersion = await extractPackageJsonVersionFromUri(TEMPLATE_URL);\n\n // Display CLI welcome banner\n showWelcome(templateVersion);\n\n // Shared theme to clear prompts after answering\n const promptTheme = { prefix: '', style: { answer: (text: string) => text } };\n const promptContext = { clearPromptOnDone: true };\n\n // Choose which template snapshot to scaffold from: a stable release or the\n // latest commit. Skipped when a custom --template is provided (ref doesn't apply).\n const templateRef = cli.options.template ? undefined : await promptTemplateRef(promptTheme, promptContext);\n\n // Prompt for project name if not provided\n if (!cli.directory) {\n cli.directory = await input(\n {\n message: 'Enter your project name',\n default: 'my-cella-app',\n theme: promptTheme,\n validate: (name) => {\n const validation = validateProjectName(basename(resolve(name)));\n return validation.valid ? true : `Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`;\n },\n },\n promptContext,\n );\n }\n\n const targetFolder = resolve(cli.directory);\n const projectName = basename(targetFolder)?.toLowerCase() || 'project';\n\n // Check if the target folder exists and is not empty\n if (existsSync(targetFolder) && !(await isEmptyDirectory(targetFolder))) {\n const dirName = cli.directory === '.' ? 'Current directory' : `Target directory \"${targetFolder}\"`;\n const message = `${dirName} is not empty. Please choose how you would like to proceed:`;\n\n const action = await select(\n {\n message,\n theme: promptTheme,\n choices: [\n { name: 'Cancel and exit', value: 'cancel' },\n { name: 'Ignore existing files and continue', value: 'ignore' },\n ],\n },\n promptContext,\n );\n\n if (action === 'cancel') {\n process.exit(1);\n }\n }\n\n // Scan sibling directories and prompt for port offset\n const portOffset = cli.options.portOffset ?? (await promptPortOffset(targetFolder, promptTheme, promptContext));\n confirmChoice('Port offset', formatOffset(portOffset));\n\n // Prompt for admin email. Default uses the reserved `example.com` domain (RFC 2606) so the\n // seed address can never resolve or route real mail — it's a placeholder, not a real inbox.\n const adminEmail =\n cli.options.adminEmail ??\n (await input(\n {\n message: 'Admin email for initial seed user',\n default: 'admin@example.com',\n theme: promptTheme,\n },\n promptContext,\n ));\n confirmChoice('Admin email', adminEmail);\n\n // Proceed with the project creation\n const createOptions: CreateOptions = {\n projectName,\n targetFolder,\n packageManager: cli.packageManager,\n templateUrl: cli.options.template,\n templateRef,\n portOffset,\n adminEmail,\n };\n\n await create(createOptions);\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n\n/**\n * Show a short intro with the two starting points (latest release and latest\n * commit, each with its date) and let the user choose which template snapshot to\n * scaffold from. Returns the template ref (release tag or commit SHA), or undefined\n * to use the default branch (when metadata is unavailable).\n */\nasync function promptTemplateRef(theme: object, context: object): Promise<string | undefined> {\n const [release, commit] = await Promise.all([fetchLatestRelease(TEMPLATE_URL), fetchLatestCommit(TEMPLATE_URL)]);\n\n // No metadata (offline / rate-limited) — fall back to the default branch silently.\n if (!release && !commit) return undefined;\n\n // One-sentence intro followed by the two data points.\n console.info(pc.dim('You are about to install the Cella template.'));\n console.info();\n\n if (release) console.info(` Latest release: ${release.tag} ${pc.dim(`· ${release.date}`)}`);\n if (commit) {\n console.info(` Latest commit: ${commit.shortSha} ${pc.dim(`· ${commit.message} · ${commit.date}`)}`);\n // Clickable link to the exact commit on GitHub, in grey below the summary line.\n const repo = TEMPLATE_URL.replace('github:', '');\n console.info(` ${pc.gray(`https://github.com/${repo}/commit/${commit.sha}`)}`);\n }\n console.info();\n\n // Only one available — use it without prompting.\n if (!release) return commit?.sha;\n if (!commit) return release.tag;\n\n const choice = await select(\n {\n message: 'Start from',\n theme,\n choices: [\n { name: 'Latest release', value: 'release' },\n { name: 'Latest commit', value: 'commit' },\n ],\n },\n context,\n );\n\n // The prompt clears itself on done, so keep the choice visible with a checkmark.\n if (choice === 'release') {\n confirmChoice('Latest release', release.tag);\n return release.tag;\n }\n confirmChoice('Latest commit', commit.shortSha);\n return commit.sha;\n}\n\n/** Format an offset as a port overview string, e.g. \"10 → :3010 / :4010 / :5442\" */\nfunction formatOffset(o: number, suffix = ''): string {\n return `${o} → :${3000 + o} / :${4000 + o} / :${5432 + o}${suffix}`;\n}\n\n/** Scan sibling forks and prompt the user to pick a port offset */\nasync function promptPortOffset(targetFolder: string, theme: object, context: object): Promise<number> {\n const usedPorts = await detectUsedPorts(targetFolder);\n const suggested = findNextOffset(usedPorts);\n\n // Fold a compact hint into the prompt message itself so it's wiped by clearPromptOnDone\n // once an offset is chosen — no leftover \"Detected forks\" trail. Override the default\n // message style (which bolds everything) so only the \"Port offset\" title is bold.\n let message = `${pc.bold('Port offset')} ${pc.dim('· to avoid conflicts with other forks/clones')}`;\n if (usedPorts.length > 0) {\n const count = usedPorts.length;\n message += pc.dim(`\\nDetected ${count} sibling fork${count === 1 ? '' : 's'} · suggested offset ${suggested}`);\n }\n const portTheme = {\n ...(theme as Record<string, unknown>),\n style: { ...((theme as { style?: object }).style ?? {}), message: (text: string) => text },\n };\n\n // Presets exclude 0 (always offered as the explicit default below) and the suggested\n // value (offered first) to avoid duplicate choices.\n const presets = [10, 20, 30].filter((o) => o !== suggested);\n const choice = await select(\n {\n message,\n theme: portTheme,\n choices: [\n ...(suggested > 0 ? [{ name: formatOffset(suggested, ' (suggested)'), value: suggested }] : []),\n { name: formatOffset(0, ' (default)'), value: 0 },\n ...presets.map((o) => ({ name: formatOffset(o), value: o })),\n { name: 'Custom offset', value: -1 },\n ],\n },\n context,\n );\n\n if (choice !== -1) return choice;\n\n const custom = await input(\n {\n message: 'Enter custom offset (0-490, multiples of 10)',\n default: String(suggested),\n theme,\n validate: (val) => {\n const n = Number(val);\n if (Number.isNaN(n) || n < 0 || n > 490) return 'Must be between 0 and 490';\n if (n % 10 !== 0) return 'Must be a multiple of 10';\n return true;\n },\n },\n context,\n );\n return Number(custom);\n}\n","import pc from 'picocolors';\nimport packageJson from '../package.json' with { type: 'json' };\n\n/** Name of this CLI tool */\nexport const NAME = 'create cella';\n\n/** Thin line divider for console output (60 chars wide) */\nexport const DIVIDER = '─'.repeat(60);\n\n/** URL of the template repository */\nexport const TEMPLATE_URL = 'github:cellajs/cella';\n\n/** URL to the repository */\nexport const CELLA_REMOTE_URL = 'git@github.com:cellajs/cella.git';\n\n/** Export details from package.json */\nexport const DESCRIPTION: string = packageJson.description;\nexport const VERSION: string = packageJson.version;\nexport const WEBSITE: string = packageJson.homepage;\n\nexport function getHeaderLine(templateVersion?: string): string {\n const leftText = `⧈ ${NAME} · ${VERSION} · cella ${templateVersion}`;\n const left = `${pc.cyan(`⧈ ${NAME}`)} ${pc.dim(`· ${VERSION} · cella ${templateVersion}`)}`;\n const right = pc.cyan(WEBSITE);\n const padding = Math.max(1, 60 - leftText.length - WEBSITE.length);\n return `${left}${' '.repeat(padding)}${right}`;\n}\n\n// Files or folders to be removed from the template after downloading\nexport const TO_REMOVE: string[] = ['./cli/create', './info/QUICKSTART.md'];\n\n// Specific folder contents to be cleaned out from the template\nexport const TO_CLEAN: string[] = ['./backend/drizzle'];\n\n// Files to copy/paste after downloading\nexport const TO_COPY: Record<string, string> = {\n './frontend/.env.example': './frontend/.env',\n './info/QUICKSTART.md': 'README.md',\n};\n\n/**\n * Placeholder config template that replaces `shared/config/config.default.ts` in new forks.\n * Ships inside the create-cella package (see package.json `files`) and is resolved relative\n * to the package — NOT the downloaded fork. Contains `__project_name__` and `__project_slug__`\n * tokens interpolated at create time.\n */\nexport const PLACEHOLDER_CONFIG = 'configs/default-config.ts.template';\n\n/**\n * Read a `.env.example` file and apply key=value replacements.\n * Comments and unmatched keys are preserved as-is.\n * Returns null if the file doesn't exist (caller should generate from scratch).\n */\nexport async function generateEnvFromExample(\n examplePath: string,\n replacements: Record<string, string>,\n): Promise<string | null> {\n const { readFile } = await import('node:fs/promises');\n let content: string;\n try {\n content = await readFile(examplePath, 'utf8');\n } catch {\n return null;\n }\n\n return content.replace(/^([A-Z_][A-Z0-9_]*)=(.*)$/gm, (match, key, _value) => {\n if (key in replacements) return `${key}=${replacements[key]}`;\n return match;\n });\n}\n\n/**\n * Replacement map for `backend/.env`.\n * The backend `.env` is the single source of truth for the project slug, database\n * ports (consumed by `backend/compose.yaml`), connection URLs, admin email and API port.\n */\nexport function getBackendEnvReplacements(\n slug: string,\n adminEmail: string,\n portOffset: number,\n): Record<string, string> {\n const db = 5432 + portOffset;\n return {\n PROJECT_SLUG: slug,\n DB_PORT: String(db),\n DB_TEST_PORT: String(5434 + portOffset),\n DATABASE_URL: `postgres://runtime_role:dev_password@0.0.0.0:${db}/postgres`,\n DATABASE_ADMIN_URL: `postgres://postgres:postgres@0.0.0.0:${db}/postgres`,\n DATABASE_CDC_URL: `postgres://admin_role:dev_password@0.0.0.0:${db}/postgres`,\n ADMIN_EMAIL: adminEmail,\n PORT: String(4000 + portOffset),\n };\n}\n\n/**\n * Generate env config files with project-specific values.\n * All configs go through the same data-driven loop.\n * Values prefixed with '=' are emitted as raw TS expressions (not quoted).\n */\nexport function generateEnvConfigs(slug: string, name: string, portOffset: number): Record<string, string> {\n const fe = 3000 + portOffset;\n const be = 4000 + portOffset;\n\n const header =\n \"import type { DeepPartial } from '../src/config-builder/types';\\nimport type _default from './config.default';\\n\";\n\n // Per-environment specs: optional imports + object props (= prefix → raw TS expression)\n const envs: Record<string, { imports?: string; props: Record<string, string | boolean> }> = {\n development: {\n props: {\n slug: `${slug}-development`,\n domain: '',\n frontendUrl: `http://localhost:${fe}`,\n backendUrl: `http://localhost:${be}`,\n backendAuthUrl: `http://localhost:${be}/auth`,\n },\n },\n staging: {\n props: {\n slug: `${slug}-staging`,\n domain: `${slug}.example.com`,\n frontendUrl: `https://staging.${slug}.example.com`,\n backendUrl: `https://api-staging.${slug}.example.com`,\n backendAuthUrl: `https://api-staging.${slug}.example.com/auth`,\n },\n },\n tunnel: {\n props: {\n frontendUrl: `https://localhost:${fe}`,\n backendUrl: `https://${slug}.ngrok.dev`,\n backendAuthUrl: `https://${slug}.ngrok.dev/auth`,\n },\n },\n test: {\n imports: \"import development from './config.development';\\n\",\n props: {\n domain: '',\n frontendUrl: '=development.frontendUrl',\n backendUrl: '=development.backendUrl',\n backendAuthUrl: '=development.backendAuthUrl',\n },\n },\n production: { props: { maintenance: false } },\n };\n\n // Serialize value: '=' prefix → raw TS expression, boolean → literal, string → quoted\n const lit = (v: string | boolean) => {\n if (typeof v === 'boolean') return String(v);\n if (v.startsWith('=')) return v.slice(1);\n return `'${v}'`;\n };\n\n const result: Record<string, string> = {};\n\n for (const [mode, { imports = '', props }] of Object.entries(envs)) {\n const nameEntry = mode !== 'production' ? ` name: '${name} ${mode.toUpperCase()}',\\n` : '';\n const body = Object.entries(props)\n .map(([k, v]) => ` ${k}: ${lit(v)},`)\n .join('\\n');\n result[`./shared/config/config.${mode}.ts`] =\n `${imports}${header}\\nexport default {\\n mode: '${mode}',\\n${nameEntry}${body}\\n} satisfies DeepPartial<typeof _default>;\\n`;\n }\n\n return result;\n}\n","{\n \"name\": \"@cellajs/create-cella\",\n \"version\": \"0.3.0\",\n \"private\": false,\n \"license\": \"MIT\",\n \"description\": \"Cella is a TypeScript template to create web apps with sync and offline capabilities.\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/cellajs/create-cella\"\n },\n \"homepage\": \"https://cellajs.com\",\n \"author\": \"CellaJS <info@cellajs.com>\",\n \"engines\": {\n \"node\": \"24.x\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"configs\",\n \"dist\",\n \"index.js\",\n \"README.md\"\n ],\n \"imports\": {\n \"#/*\": \"./src/*\"\n },\n \"bin\": {\n \"create-cella\": \"index.js\"\n },\n \"scripts\": {\n \"start\": \"tsx ./src/create-cella-cli.ts\",\n \"try\": \"tsx ./src/create-cella-cli.ts ../cella-try-app\",\n \"clean\": \"rimraf ./dist\",\n \"build\": \"tsup\",\n \"test-build\": \"pnpm run build && node index.js\",\n \"prepublishOnly\": \"pnpm run test:release\",\n \"check\": \"pnpm ts && pnpm biome check --write .\",\n \"ts\": \"tsgo --pretty\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"test\": \"vitest run\",\n \"test:release\": \"pnpm run build && vitest run --config vitest.release.config.ts\",\n \"test:watch\": \"vitest\",\n \"prepare\": \"lefthook install\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^8.5.2\",\n \"commander\": \"^15.0.0\",\n \"ignore\": \"^7.0.5\",\n \"isomorphic-git\": \"^1.38.5\",\n \"nanotar\": \"^0.3.0\",\n \"ora\": \"^9.4.1\",\n \"picocolors\": \"^1.1.1\",\n \"validate-npm-package-name\": \"^8.0.0\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.5.1\",\n \"@commitlint/cli\": \"^21.1.0\",\n \"@commitlint/config-conventional\": \"^21.1.0\",\n \"@evilmartians/lefthook\": \"^2.1.9\",\n \"@types/node\": \"24.10.13\",\n \"@types/validate-npm-package-name\": \"^4.0.2\",\n \"@typescript/native-preview\": \"7.0.0-dev.20260628.1\",\n \"rimraf\": \"^6.1.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.22.4\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.9\"\n },\n \"packageManager\": \"pnpm@11.8.0\",\n \"commitlint\": {\n \"extends\": [\n \"@commitlint/config-conventional\"\n ],\n \"rules\": {\n \"type-enum\": [\n 2,\n \"always\",\n [\n \"feat\",\n \"fix\",\n \"perf\",\n \"refactor\",\n \"revert\",\n \"docs\",\n \"chore\",\n \"build\",\n \"ci\",\n \"style\",\n \"test\"\n ]\n ]\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { cp, mkdir, rm } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { checkbox } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { addRemote } from '#/add-remote';\nimport { TEMPLATE_URL } from '#/constants';\nimport { type CreateOptions, confirmChoice, showSuccess } from '#/modules/cli';\nimport { cleanTemplate } from '#/utils/clean-template';\nimport { downloadGithubTemplate } from '#/utils/download-template';\nimport { gitAddAll, gitCommit, gitInit } from '#/utils/git';\nimport { listTemplateFiles } from '#/utils/list-template-files';\nimport { createProgress, pauseSpinner, resumeSpinner } from '#/utils/progress';\nimport { optionalModuleFolders, scanOptionalModules } from '#/utils/scan-optional-modules';\n\n/** Check if a path is a local directory */\nfunction isLocalPath(path: string): boolean {\n return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');\n}\n\nfunction shouldSkipStep(name: 'git' | 'remote'): boolean {\n return process.env[`CREATE_CELLA_SKIP_${name.toUpperCase()}`] === 'true';\n}\n\nexport async function create({\n projectName,\n targetFolder,\n packageManager,\n templateUrl,\n templateRef,\n portOffset,\n adminEmail,\n silent = false,\n}: CreateOptions): Promise<void> {\n // Save the original working directory\n const originalCwd = process.cwd();\n\n // Use custom template or default\n const template = templateUrl || TEMPLATE_URL;\n const isLocalTemplate = templateUrl && isLocalPath(templateUrl);\n\n const progress = createProgress('creating project', silent);\n\n try {\n // Create the target folder\n progress.step('creating project folder');\n await mkdir(targetFolder, { recursive: true });\n process.chdir(targetFolder);\n\n // Download or copy the template\n if (isLocalTemplate) {\n progress.step('copying local template');\n const sourcePath = resolve(originalCwd, templateUrl);\n\n // Check if target is inside source (would cause EINVAL). Use a path-boundary check\n // (not a string prefix) so siblings like `cella-try-app` next to `cella` aren't\n // misdetected as being inside the template.\n const rel = relative(sourcePath, targetFolder);\n const targetInsideTemplate = rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n if (targetInsideTemplate) {\n throw new Error(\n 'Cannot create project inside the template directory.\\n' +\n ` Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`,\n );\n }\n\n // Copy only what git would track (respects .gitignore: no build output, no local .env secrets).\n const tracked = await listTemplateFiles(sourcePath);\n for (const rel of tracked) {\n const src = join(sourcePath, rel);\n if (!existsSync(src)) continue; // listed but deleted in the working tree\n const dest = join(targetFolder, rel);\n await mkdir(dirname(dest), { recursive: true });\n await cp(src, dest);\n }\n } else {\n // Pin to the chosen ref (release tag or commit SHA), else the default branch.\n const ref = templateRef ?? 'main';\n progress.step(`downloading cella template${templateRef ? ` (${templateRef})` : ''}`);\n await downloadGithubTemplate(template, ref, targetFolder);\n }\n\n // Ask which optional modules to keep, then drop deselected paths\n if (!silent) await promptOptionalModules(targetFolder);\n\n // Clean the template, generate configs\n progress.step('cleaning template');\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });\n\n // Initialize git repository\n const gitFolderPath = join(targetFolder, '.git');\n if (!shouldSkipStep('git') && !existsSync(gitFolderPath)) {\n progress.step('initializing git');\n await gitInit(targetFolder);\n await gitAddAll(targetFolder);\n await gitCommit(targetFolder, 'Initial commit');\n }\n\n // Add upstream remote\n if (!shouldSkipStep('remote')) {\n progress.step('adding upstream remote');\n await addRemote({ targetFolder, silent: true });\n }\n\n // Done\n progress.done(pc.bold(`created ${projectName}`));\n } catch (error) {\n progress.fail(error instanceof Error ? error.message : String(error));\n // Rethrow so callers (CLI entry, tests) see the real failure. The CLI entry\n // turns this into a non-zero exit; swallowing it here with process.exit hid\n // the underlying error from CI and vitest.\n throw error;\n }\n\n // Check if the working directory needs to be changed\n const needsCd = originalCwd !== targetFolder;\n const relativePath = relative(originalCwd, targetFolder);\n\n // Display final success message\n showSuccess(projectName, targetFolder, relativePath, needsCd, packageManager);\n}\n\n/**\n * Scan the downloaded template for optional modules and let the user deselect them.\n * Deselected modules have their whole folder removed before cleaning. Default keeps\n * everything (parity with previous behavior).\n */\nasync function promptOptionalModules(targetFolder: string): Promise<void> {\n const modules = await scanOptionalModules(targetFolder);\n if (modules.length === 0) return;\n\n // Non-interactive (CI, piped stdin, no TTY): can't render a checkbox — keep every module\n // rather than crashing with \"User force closed the prompt\". Matches the default-keep behavior.\n if (!process.stdin.isTTY) return;\n\n pauseSpinner();\n // Blank line to separate the preceding progress step from this prompt.\n console.info();\n const keep = await checkbox(\n {\n message: 'Optional modules to include',\n // Leading space on the radio icons adds a gap after the cursor (inquirer renders\n // `${cursor}${checkbox} ${name}` with no space between cursor and checkbox).\n theme: { icon: { checked: ` ${pc.green('◉')}`, unchecked: ' ◯' } },\n choices: modules.map((m) => ({\n name: `${m.name} ${pc.dim(`· ${m.description}`)}`,\n value: m.name,\n checked: true,\n })),\n },\n // Clear the answered prompt so we can print a compact, non-bold summary that lists\n // only the kept module names (not their descriptions).\n { clearPromptOnDone: true },\n );\n\n // Keep the choice visible after the prompt clears: plain label plus the selected\n // module names only (not their descriptions), or \"none\" when everything was deselected.\n confirmChoice('Optional modules', keep.length ? keep.join(', ') : pc.dim('none'));\n resumeSpinner();\n\n // Remove every folder owned by a deselected module. `optionalModuleFolders` lists more\n // route variants than any single module uses; `force: true` ignores the ones absent here.\n const folders = modules.filter((m) => !keep.includes(m.name)).flatMap(optionalModuleFolders);\n await Promise.all(folders.map((f) => rm(join(targetFolder, f), { recursive: true, force: true })));\n}\n","import fs from 'node:fs';\nimport git from 'isomorphic-git';\n\n/**\n * Git helpers backed by isomorphic-git (pure JS) instead of shelling out to the\n * `git` binary. This keeps `create-cella` free of `child_process` (no shell access)\n * and removes any dependency on the user's global git config — the initial commit\n * uses an explicit author, so scaffolding never fails with \"Author identity unknown\".\n */\n\n/** Author used for the scaffold's initial commit (users re-author their own commits later). */\nconst INITIAL_AUTHOR = { name: 'cella', email: 'info@cellajs.com' } as const;\n\n/**\n * Initializes a new Git repository in the specified directory.\n */\nexport async function gitInit(repoPath: string): Promise<void> {\n await git.init({ fs, dir: repoPath, defaultBranch: 'main' });\n}\n\n/**\n * Stages all files in the repository (respects `.gitignore`).\n */\nexport async function gitAddAll(repoPath: string): Promise<void> {\n await git.add({ fs, dir: repoPath, filepath: '.' });\n}\n\n/**\n * Creates a commit with the specified message and returns the commit SHA.\n */\nexport async function gitCommit(repoPath: string, message: string): Promise<string> {\n return git.commit({ fs, dir: repoPath, message, author: INITIAL_AUTHOR });\n}\n\n/**\n * Gets the URL of a remote. Throws when the remote does not exist (mirrors\n * `git remote get-url` failing) so callers can detect a missing remote.\n */\nexport async function gitRemoteGetUrl(repoPath: string, remoteName: string): Promise<string> {\n const remotes = await git.listRemotes({ fs, dir: repoPath });\n const match = remotes.find((r) => r.remote === remoteName);\n if (!match) throw new Error(`No such remote '${remoteName}'`);\n return match.url;\n}\n\n/**\n * Adds a new remote.\n */\nexport async function gitRemoteAdd(repoPath: string, remoteName: string, remoteUrl: string): Promise<void> {\n await git.addRemote({ fs, dir: repoPath, remote: remoteName, url: remoteUrl });\n}\n\n/**\n * Removes a remote.\n */\nexport async function gitRemoteRemove(repoPath: string, remoteName: string): Promise<void> {\n await git.deleteRemote({ fs, dir: repoPath, remote: remoteName });\n}\n","import { CELLA_REMOTE_URL } from '#/constants';\nimport type { AddRemoteOptions } from '#/modules/cli';\nimport { gitRemoteAdd, gitRemoteGetUrl, gitRemoteRemove } from '#/utils/git';\n\n/**\n * Adds or updates the upstream remote for the Cella template.\n * @param options - Configuration options\n * @param options.silent - If true, don't throw on failure (used when called from progress tracker)\n */\nexport async function addRemote({\n targetFolder,\n remoteUrl = CELLA_REMOTE_URL,\n remoteName = 'upstream',\n silent = false,\n}: AddRemoteOptions): Promise<void> {\n try {\n // Check if the remote exists\n let remote: string | null = null;\n\n try {\n remote = await gitRemoteGetUrl(targetFolder, remoteName);\n } catch {\n // If the remote doesn't exist, it throws a fatal error\n remote = null;\n }\n\n // Add or update the remote if it doesn't exist or differs from `remoteUrl`\n if (!remote) {\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n } else if (remote !== remoteUrl) {\n // Remove existing remote and set the new URL\n await gitRemoteRemove(targetFolder, remoteName);\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n }\n } catch (error) {\n if (!silent) {\n throw error;\n }\n }\n}\n","import { basename, resolve } from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { NAME, VERSION } from '#/constants';\nimport { validateProjectName } from '#/utils/validate-project-name';\nimport type { CLIConfig, CLIOptions } from './types';\n\nfunction parsePortOffset(value: string): number {\n const parsed = Number(value);\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 490) {\n throw new InvalidArgumentError('Port offset must be an integer between 0 and 490');\n }\n\n if (parsed % 10 !== 0) {\n throw new InvalidArgumentError('Port offset must be a multiple of 10');\n }\n\n return parsed;\n}\n\n// Initialize CLI variables\nlet directory: string | null = null;\nconst packageManager = 'pnpm';\n\n/**\n * Defines the root CLI command using Commander.\n * This command accepts CLI options and validates user input.\n */\nexport const command = new Command(NAME)\n .version(VERSION, '-v, --version', `output the current version of ${NAME}`)\n .argument('[directory]', 'the directory name for the new project')\n .usage('[directory] [options]')\n .helpOption('-h, --help', 'display this help message')\n .option('--template <path>', 'use a custom template (local path or github:user/repo)')\n .option('--port-offset <number>', 'set the port offset (0-490 in steps of 10)', parsePortOffset)\n .option('--admin-email <email>', 'set the admin email for the initial seed user')\n .action((name: string) => {\n const trimmedName = typeof name === 'string' ? name.trim() : name;\n\n if (trimmedName) {\n const validation = validateProjectName(basename(resolve(trimmedName)));\n\n if (!validation.valid) {\n throw new InvalidArgumentError(`Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`);\n }\n\n directory = trimmedName;\n }\n })\n .parse();\n\n// Gather the CLI options and arguments\nconst options: CLIOptions = command.opts<CLIOptions>();\n\n/**\n * Runs the CLI and returns the parsed configuration.\n * This function parses command line arguments and returns the CLI config.\n */\nexport function runCli(): CLIConfig {\n return {\n options,\n args: command.args,\n directory,\n packageManager,\n };\n}\n\n// Export CLI configuration for direct import\nexport const cli: CLIConfig = runCli();\n","import validate from 'validate-npm-package-name';\n\ninterface ValidationResult {\n valid: boolean;\n problems?: string[];\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n const nameValidation = validate(name);\n\n if (nameValidation.validForNewPackages) {\n return { valid: true };\n }\n\n return {\n valid: false,\n problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])],\n };\n}\n","import pc from 'picocolors';\nimport { DESCRIPTION, DIVIDER, getHeaderLine } from '#/constants';\n\n/** ASCII art logo for the CLI welcome screen. */\nfunction showAscii(): void {\n console.info(pc.cyan(' _ _ '));\n console.info(pc.cyan('▒▓█████▓▒ ___ ___| | | __ _ '));\n console.info(pc.cyan('▒▓█ █▓▒ / __/ _ \\\\ | |/ _` | '));\n console.info(pc.cyan('▒▓█ █▓▒ | (_| __/ | | (_| | '));\n console.info(pc.cyan('▒▓█████▓▒ \\\\___\\\\___|_|_|\\\\__,_| '));\n}\n\n/**\n * Displays the compact CLI welcome header.\n * @param templateVersion - The version of the cella template being used\n */\nexport function showWelcome(templateVersion: string): void {\n console.info();\n showAscii();\n console.info();\n console.info(pc.dim(DESCRIPTION));\n console.info();\n console.info(getHeaderLine(templateVersion));\n console.info(DIVIDER);\n}\n\n/**\n * Print a cleared prompt's outcome as a persistent confirmation line:\n * `✓ <label> · <value>`, followed by a blank separator line. Used after prompts\n * that clear themselves on done so the chosen value stays visible.\n */\nexport function confirmChoice(label: string, value: string): void {\n console.info(`${pc.green('✓')} ${label} · ${value}`);\n console.info();\n}\n\n/**\n * Displays the final success message after project creation.\n */\nexport function showSuccess(\n projectName: string,\n _targetFolder: string,\n relativePath: string,\n needsCd: boolean,\n packageManager: string,\n): void {\n console.info();\n\n console.info(pc.bold('Next steps'));\n console.info(DIVIDER);\n console.info();\n\n if (needsCd) {\n console.info(`${pc.dim('$')} cd ${pc.cyan(relativePath)}`);\n }\n console.info(`${pc.dim('$')} ${pc.cyan(`${packageManager} install`)}`);\n console.info(`${pc.dim('$')} ${pc.cyan(`${packageManager} generate`)}`);\n console.info(\n `${pc.dim('$')} ${pc.cyan(`${packageManager} docker`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} seed`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} dev`)}`,\n );\n console.info();\n\n // Point to the generated README for first-run details and sign-in credentials. Use the\n // path relative to the new project so the terminal linkifies the right file (not this CLI's).\n const readmePath = relativePath ? `${relativePath}/README.md` : 'README.md';\n console.info(`${pc.gray('To get started building, please read:')} ${pc.cyan(readmePath)}`);\n console.info();\n console.info(`🥳 enjoy building ${pc.green(projectName)} with cella!`);\n console.info();\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\nimport {\n generateEnvConfigs,\n generateEnvFromExample,\n getBackendEnvReplacements,\n PLACEHOLDER_CONFIG,\n TO_CLEAN,\n TO_COPY,\n TO_REMOVE,\n} from '#/constants';\n\nconst warningMark = pc.yellow('⚠');\n\n/**\n * Resolve the placeholder config template that ships inside the create-cella package.\n *\n * The template is the CLI's own asset (listed in package.json `files`), NOT part of the\n * downloaded fork. It must therefore be read relative to this module, which lives at:\n * - dev (tsx): `<pkg>/src/utils/clean-template.ts` → template at `../../<PLACEHOLDER_CONFIG>`\n * - bundled: `<pkg>/dist/index.js` → template at `../<PLACEHOLDER_CONFIG>`\n */\nasync function resolvePlaceholderConfigPath(): Promise<string> {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [path.resolve(here, '../..', PLACEHOLDER_CONFIG), path.resolve(here, '..', PLACEHOLDER_CONFIG)];\n for (const candidate of candidates) {\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n // try next candidate\n }\n }\n // Fall back to the first candidate so the caller surfaces a clear ENOENT.\n return candidates[0];\n}\n\n/**\n * Cleans the specified template by removing designated folders and files.\n * @param params - Parameters containing the target folder, project name, and optional extra edits.\n */\nexport async function cleanTemplate({\n targetFolder,\n projectName,\n displayName,\n portOffset = 0,\n adminEmail = `admin@${projectName}.example.com`,\n}: {\n targetFolder: string;\n projectName: string;\n displayName: string;\n portOffset?: number;\n adminEmail?: string;\n}): Promise<void> {\n // Change the current working directory to targetFolder if not already set\n if (process.cwd() !== targetFolder) {\n process.chdir(targetFolder);\n }\n\n return new Promise<void>((resolve, reject) => {\n (async () => {\n try {\n // Copy specified files\n for (const [src, dest] of Object.entries(TO_COPY)) {\n const srcAbsolutePath = path.resolve(targetFolder, src);\n const destAbsolutePath = path.resolve(targetFolder, dest);\n await copyFile(srcAbsolutePath, destAbsolutePath);\n }\n\n // Replace config.default.ts with interpolated placeholder config\n await applyPlaceholderConfig(targetFolder, projectName);\n\n // Generate backend .env from backend/.env.example.\n // The backend .env is the single source of truth for the project slug and DB ports\n // (consumed by backend/compose.yaml). There is no root .env.\n const backendReplacements = getBackendEnvReplacements(projectName, adminEmail, portOffset);\n const backendEnv = await generateEnvFromExample(\n path.resolve(targetFolder, 'backend/.env.example'),\n backendReplacements,\n );\n if (backendEnv) {\n await fs.writeFile(path.resolve(targetFolder, 'backend/.env'), backendEnv, 'utf8');\n }\n\n // Generate minimal env config files with project values and ports baked in\n const envConfigs = generateEnvConfigs(projectName, displayName, portOffset);\n await Promise.all(\n Object.entries(envConfigs).map(([filePath, content]) =>\n fs.writeFile(path.resolve(targetFolder, filePath), content, 'utf8'),\n ),\n );\n\n // Clean specified folder contents\n await Promise.all(\n TO_CLEAN.map((folderPath) => {\n const absolutePath = path.resolve(targetFolder, folderPath);\n return removeFolderContents(absolutePath);\n }),\n );\n\n // Remove specified files and folders\n await Promise.all(\n TO_REMOVE.map((filePath) => {\n const absolutePath = path.resolve(targetFolder, filePath);\n return removeFileOrFolder(absolutePath);\n }),\n );\n\n resolve();\n } catch (err) {\n reject(`Error during the cleaning process: ${err}`);\n }\n })();\n });\n}\n\n/**\n * Removes all contents within a specified folder.\n * @param folderPath - The path of the folder to clean.\n */\nasync function removeFolderContents(folderPath: string): Promise<void> {\n // List all files in the folder\n const files = await fs.readdir(folderPath);\n\n await Promise.all(\n files.map(async (file) => {\n const filePath = path.join(folderPath, file);\n\n // Get the file or folder statistics\n const stat = await fs.lstat(filePath);\n\n // If it's a directory, remove it and all its contents\n if (stat.isDirectory()) {\n await fs.rm(filePath, { recursive: true, force: true });\n } else {\n // If it's a file, remove it\n await fs.rm(filePath);\n }\n }),\n );\n}\n\n/**\n * Removes a specified file or folder.\n * @param pathToRemove - The path to the file or folder to remove.\n */\nasync function removeFileOrFolder(pathToRemove: string): Promise<void> {\n await fs.rm(pathToRemove, { recursive: true, force: true });\n}\n\n/**\n * Helper function to copy files if the source exists.\n * @param src - The source file path.\n * @param dest - The destination file path.\n */\nasync function copyFile(src: string, dest: string): Promise<void> {\n try {\n // Check if the source file exists\n await fs.access(src);\n\n // Ensure the destination directory exists\n await fs.mkdir(path.dirname(dest), { recursive: true });\n\n // Copy the file\n await fs.copyFile(src, dest);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Source file \"${src}\" does not exist > Skip copy`);\n } else {\n throw err;\n }\n }\n}\n\n/**\n * Read the placeholder config template, interpolate project tokens, and\n * write it as `shared/config/config.default.ts` — replacing the original.\n */\nasync function applyPlaceholderConfig(targetFolder: string, projectName: string): Promise<void> {\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n\n const src = await resolvePlaceholderConfigPath();\n const dest = path.resolve(targetFolder, './shared/config/config.default.ts');\n\n try {\n let content = await fs.readFile(src, 'utf8');\n content = content.replaceAll('__project_name__', displayName);\n content = content.replaceAll('__project_slug__', projectName);\n await fs.writeFile(dest, content, 'utf8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Placeholder config \"${src}\" not found > Skip`);\n } else {\n throw err;\n }\n }\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { parseTarGzip } from 'nanotar';\n\n/** Parse `github:owner/repo` or `owner/repo` into its owner and repo parts. */\nfunction parseGithubRepo(template: string): { owner: string; repo: string } {\n const [owner, repo] = template.replace(/^github:/, '').split('/');\n if (!owner || !repo) {\n throw new Error(`Invalid template '${template}'. Expected 'github:owner/repo'.`);\n }\n return { owner, repo };\n}\n\n/**\n * Downloads a GitHub repository tarball and extracts it into `targetFolder`.\n *\n * Uses the codeload `tar.gz` endpoint with a plain `fetch` and pure-JS gunzip +\n * untar (nanotar, via the Web `DecompressionStream`). No git, no `child_process`,\n * so `create-cella` keeps a clean supply-chain profile (no shell access).\n *\n * @param template - `github:owner/repo` (or `owner/repo`).\n * @param ref - Release tag, commit SHA, or branch to download.\n * @param targetFolder - Directory to extract the template into.\n */\nexport async function downloadGithubTemplate(template: string, ref: string, targetFolder: string): Promise<void> {\n const { owner, repo } = parseGithubRepo(template);\n const url = `https://codeload.github.com/${owner}/${repo}/tar.gz/${ref}`;\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to download template ${owner}/${repo}@${ref}: ${response.status} ${response.statusText}`);\n }\n\n const archive = new Uint8Array(await response.arrayBuffer());\n const entries = await parseTarGzip(archive);\n\n for (const entry of entries) {\n if (entry.type !== 'file' || !entry.data) continue;\n // GitHub nests everything under a top-level `repo-ref/` folder — strip it.\n const relativePath = entry.name.replace(/^[^/]+\\//, '');\n if (!relativePath) continue;\n\n const dest = join(targetFolder, relativePath);\n await mkdir(dirname(dest), { recursive: true });\n // Preserve the executable bit (nanotar reports mode as an octal string).\n const mode = entry.attrs?.mode ? Number.parseInt(entry.attrs.mode, 8) : undefined;\n await writeFile(dest, entry.data, mode ? { mode } : undefined);\n }\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport ignore from 'ignore';\n\n/**\n * Lists the files under `root` that git would track, without shelling out to git.\n *\n * Honors the root `.gitignore` (via the `ignore` package) and always skips `.git`\n * and `node_modules`. Used for the local-template dev flow (`--template ./local`)\n * so build output and local `.env` secrets aren't copied into the new project.\n *\n * Paths are returned relative to `root` using POSIX separators.\n */\nexport async function listTemplateFiles(root: string): Promise<string[]> {\n const ig = ignore().add(['.git', 'node_modules']);\n try {\n ig.add(await readFile(join(root, '.gitignore'), 'utf8'));\n } catch {\n // No .gitignore at the template root — only the defaults above apply.\n }\n\n const files: string[] = [];\n\n async function walk(relDir: string): Promise<void> {\n const entries = await readdir(relDir ? join(root, relDir) : root, { withFileTypes: true });\n for (const entry of entries) {\n const rel = relDir ? `${relDir}/${entry.name}` : entry.name;\n // `ignore` matches directory rules when the path ends with a slash.\n if (ig.ignores(entry.isDirectory() ? `${rel}/` : rel)) continue;\n if (entry.isDirectory()) await walk(rel);\n else if (entry.isFile()) files.push(rel);\n }\n }\n\n await walk('');\n return files;\n}\n","/**\n * Progress Tracker Utility\n *\n * Provides compact progress tracking for CLI operations.\n * Uses a single spinner that updates in place.\n */\n\nimport ora, { type Ora } from 'ora';\nimport pc from 'picocolors';\n\n/** Global reference to the currently active spinner */\nlet activeSpinner: Ora | null = null;\n\n/** Pause the active spinner (for interactive prompts) */\nexport function pauseSpinner(): void {\n if (activeSpinner) {\n activeSpinner.stop();\n }\n}\n\n/** Resume the active spinner after a prompt */\nexport function resumeSpinner(): void {\n if (activeSpinner) {\n activeSpinner.start();\n }\n}\n\nexport interface ProgressTracker {\n /** Update the current step (shown as spinner text) */\n step: (message: string) => void;\n /** Mark the tracker as complete with final message */\n done: (message: string) => void;\n /** Mark the tracker as failed with error message */\n fail: (message: string) => void;\n}\n\n/**\n * Creates a progress tracker for a multi-step operation.\n * Shows a single spinner that updates in place.\n *\n * @param title - The initial title for the spinner\n * @returns A ProgressTracker instance\n *\n * @example\n * const progress = createProgress('creating project');\n * progress.step('downloading template');\n * progress.step('installing dependencies');\n * progress.done('project created');\n */\nexport function createProgress(title: string, silent = false): ProgressTracker {\n const completedSteps: string[] = [];\n\n const spinner = ora({\n text: title,\n isSilent: silent,\n });\n\n activeSpinner = spinner;\n spinner.start();\n\n // Helper to log (respects silent mode)\n const log = (msg: string) => {\n if (!silent) console.info(msg);\n };\n\n return {\n step: (message: string) => {\n // Complete previous step with green check if there was one\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n }\n completedSteps.push(message);\n spinner.text = message;\n spinner.start();\n },\n\n done: (message: string) => {\n // Complete the last step with green check\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n } else {\n spinner.stop();\n }\n activeSpinner = null;\n if (message) {\n log('');\n log(`${pc.green('✓')} ${message}`);\n }\n },\n\n fail: (message: string) => {\n spinner.stop();\n activeSpinner = null;\n log(`${pc.red('✗')} ${pc.red(message)}`);\n },\n };\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\n\n/** An optional module discovered in a `*-module.ts` registration. */\nexport interface OptionalModule {\n name: string;\n description: string;\n /** Module folder, relative to the project root, removed when deselected. */\n folder: string;\n}\n\n/** Directories never worth scanning. */\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage']);\n\n/**\n * Scan the template for `*-module.ts` files that register `optional: true` and return\n * their name, description and folder. Pure static regex parsing — no imports/eval — so it\n * runs before dependencies are installed. The folder is the module file's directory.\n */\nexport async function scanOptionalModules(targetFolder: string): Promise<OptionalModule[]> {\n const files: string[] = [];\n await collectModuleFiles(targetFolder, files);\n\n const modules: OptionalModule[] = [];\n for (const file of files) {\n const src = await readFile(file, 'utf8');\n if (!/optional:\\s*true/.test(src)) continue;\n\n const name = src.match(/name:\\s*['\"]([^'\"]+)['\"]/)?.[1];\n const description = src.match(/description:\\s*['\"]([^'\"]+)['\"]/)?.[1] ?? '';\n if (!name) continue;\n\n modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });\n }\n return modules.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Every project-root-relative folder removed when an optional module is deselected: its own\n * module folder plus the route and static-asset folders it may own. Route folders are\n * speculative — a module owns either a pathless `_<name>` (URL-transparent) or a path-based\n * `<name>` route, under `routes/_public` and/or `routes/_app` — so callers remove only the\n * variants that actually exist. Single-sourced here so removal and tests stay in sync.\n */\nexport function optionalModuleFolders({ name, folder }: OptionalModule): string[] {\n return [\n folder,\n `frontend/src/routes/_public/_${name}`,\n `frontend/src/routes/_public/${name}`,\n `frontend/src/routes/_app/_${name}`,\n `frontend/src/routes/_app/${name}`,\n `frontend/public/static/${name}`,\n ];\n}\n\nasync function collectModuleFiles(dir: string, out: string[]): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await collectModuleFiles(join(dir, entry.name), out);\n } else if (entry.name.endsWith('-module.ts')) {\n out.push(join(dir, entry.name));\n }\n }\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\ninterface UsedPorts {\n project: string;\n frontend: number;\n backend: number;\n offset: number;\n}\n\n/**\n * Scan sibling directories for existing cella forks and extract their dev ports.\n * Looks for `shared/config/config.development.ts` to identify cella-based projects.\n */\nexport async function detectUsedPorts(targetFolder: string): Promise<UsedPorts[]> {\n const parentDir = dirname(targetFolder);\n const used: UsedPorts[] = [];\n\n let siblings: string[];\n try {\n siblings = await readdir(parentDir);\n } catch {\n return used;\n }\n\n for (const name of siblings) {\n const configPath = join(parentDir, name, 'shared/config/config.development.ts');\n try {\n const content = await readFile(configPath, 'utf8');\n const feMatch = content.match(/frontendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n const beMatch = content.match(/backendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n if (feMatch && beMatch) {\n const frontend = Number(feMatch[1]);\n const backend = Number(beMatch[1]);\n used.push({\n project: name,\n frontend,\n backend,\n offset: frontend - 3000,\n });\n }\n } catch {\n // Not a cella fork, skip\n }\n }\n\n return used;\n}\n\n/** Find the next available offset (in steps of 10) that doesn't collide with existing forks. */\nexport function findNextOffset(usedPorts: UsedPorts[]): number {\n const usedOffsets = new Set(usedPorts.map((p) => p.offset));\n for (let offset = 0; offset <= 490; offset += 10) {\n if (!usedOffsets.has(offset)) return offset;\n }\n return 0;\n}\n","/**\n * Retrieves the version from the package.json file of a GitHub repository.\n * If the package.json file is not found or an error occurs, it returns 'unknown'.\n *\n * @param repositoryUrl {string} - The GitHub repository URL in the format 'github:user/repo'.\n * @param branch {string} - The branch name (defaults to 'main').\n * @returns {Promise<string>} - The version from the package.json file.\n */\nexport async function extractPackageJsonVersionFromUri(repositoryUrl: string, branch = 'main'): Promise<string> {\n // Extract owner and repo from the URL\n const [owner, repo] = repositoryUrl.replace('github:', '').split('/');\n\n // Use the GitHub contents API to fetch the package.json file for the given branch\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${branch}`;\n\n try {\n const response = await fetch(apiUrl, {\n headers: { Accept: 'application/vnd.github.raw+json' },\n });\n if (!response.ok) return 'unknown';\n\n const packageJson = (await response.json()) as { version?: string };\n\n // Return the version from the package.json, or 'unknown' if not found\n return packageJson.version || 'unknown';\n } catch {\n // If there's an error (file not found, etc.), return 'unknown'\n return 'unknown';\n }\n}\n","/**\n * Fetch lightweight metadata about the cella template from the GitHub API so the\n * scaffolder can offer \"latest release\" vs \"latest commit\" as a starting point.\n *\n * Both helpers fail soft (return null) on network/API errors or rate limits — the\n * caller falls back to the default branch when metadata is unavailable.\n */\n\n/** Parse 'github:owner/repo' (or 'owner/repo') into [owner, repo]. */\nfunction parseRepo(repositoryUrl: string): [string, string] {\n const [owner, repo] = repositoryUrl.replace('github:', '').split('/');\n return [owner, repo];\n}\n\n/** Format an ISO timestamp as a locale-independent YYYY-MM-DD date. */\nfunction toDate(iso?: string): string {\n return iso ? iso.slice(0, 10) : 'unknown';\n}\n\nconst GITHUB_HEADERS = { Accept: 'application/vnd.github+json' } as const;\n\nexport interface ReleaseInfo {\n /** Release tag, e.g. 'v0.5.0' */\n tag: string;\n /** Publication date, YYYY-MM-DD */\n date: string;\n}\n\nexport interface CommitInfo {\n /** Full commit SHA (used for a reproducible checkout) */\n sha: string;\n /** Short 7-char SHA for display */\n shortSha: string;\n /** First line of the commit message */\n message: string;\n /** Commit date, YYYY-MM-DD */\n date: string;\n}\n\n/**\n * Fetch the latest published template release (`v*` tag).\n *\n * cella publishes two release series — the template (`v*`) and the scaffolder\n * (`create-cella-v*`). We list releases (newest first) and pick the first\n * non-draft, non-prerelease `v*` release so the scaffolder version is ignored.\n */\nexport async function fetchLatestRelease(repositoryUrl: string): Promise<ReleaseInfo | null> {\n const [owner, repo] = parseRepo(repositoryUrl);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=30`;\n\n try {\n const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });\n if (!response.ok) return null;\n\n const releases = (await response.json()) as Array<{\n tag_name?: string;\n published_at?: string;\n draft?: boolean;\n prerelease?: boolean;\n }>;\n\n const release = releases.find((r) => !r.draft && !r.prerelease && /^v\\d/.test(r.tag_name ?? ''));\n if (!release?.tag_name) return null;\n\n return { tag: release.tag_name, date: toDate(release.published_at) };\n } catch {\n return null;\n }\n}\n\n/** Fetch the latest commit on a branch (defaults to 'main'). */\nexport async function fetchLatestCommit(repositoryUrl: string, branch = 'main'): Promise<CommitInfo | null> {\n const [owner, repo] = parseRepo(repositoryUrl);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`;\n\n try {\n const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });\n if (!response.ok) return null;\n\n const data = (await response.json()) as {\n sha?: string;\n commit?: { message?: string; author?: { date?: string } };\n };\n if (!data.sha) return null;\n\n const message = (data.commit?.message ?? '').split('\\n')[0];\n return { sha: data.sha, shortSha: data.sha.slice(0, 7), message, date: toDate(data.commit?.author?.date) };\n } catch {\n return null;\n }\n}\n","import { readdir } from 'node:fs/promises';\n\n/**\n * Checks if a directory is empty or only contains a .git directory.\n *\n * @param path - The path of the directory to check.\n * @returns Resolves to true if the directory is empty or contains only a .git folder, false otherwise.\n * @throws Throws an error if the path is not a directory or if there's an issue reading the directory.\n */\nexport async function isEmptyDirectory(path: string): Promise<boolean> {\n const files = await readdir(path);\n\n // Check if directory is empty or contains only the .git directory\n return files.length === 0 || (files.length === 1 && files[0] === '.git');\n}\n"],"mappings":"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","names":["existsSync","basename","resolve","pc","readFile","mkdir","dirname","join","relative","resolve","pc","pc","pc","packageManager","fs","pc","pc","fs","resolve","join","pc","readdir","readFile","dirname","join","path","packageManager","mkdir","resolve","relative","rel","join","dirname","pc","readdir","readFile","dirname","join","readdir","path","basename","resolve","existsSync","pc"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cellajs/create-cella",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Cella is a TypeScript template to create web apps with sync and offline capabilities.",
@@ -9,8 +9,7 @@
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/cellajs/cella",
13
- "directory": "cli/create-cella"
12
+ "url": "https://github.com/cellajs/create-cella"
14
13
  },
15
14
  "homepage": "https://cellajs.com",
16
15
  "author": "CellaJS <info@cellajs.com>",
@@ -34,24 +33,54 @@
34
33
  "dependencies": {
35
34
  "@inquirer/prompts": "^8.5.2",
36
35
  "commander": "^15.0.0",
37
- "giget": "^3.3.0",
38
- "nano-spawn": "^2.0.0",
36
+ "ignore": "^7.0.5",
37
+ "isomorphic-git": "^1.38.5",
38
+ "nanotar": "^0.3.0",
39
39
  "ora": "^9.4.1",
40
40
  "picocolors": "^1.1.1",
41
41
  "validate-npm-package-name": "^8.0.0"
42
42
  },
43
43
  "devDependencies": {
44
+ "@biomejs/biome": "^2.5.1",
45
+ "@commitlint/cli": "^21.1.0",
46
+ "@commitlint/config-conventional": "^21.1.0",
47
+ "@evilmartians/lefthook": "^2.1.9",
44
48
  "@types/node": "24.10.13",
45
49
  "@types/validate-npm-package-name": "^4.0.2",
46
50
  "@typescript/native-preview": "7.0.0-dev.20260628.1",
51
+ "rimraf": "^6.1.3",
47
52
  "tsup": "^8.5.1",
48
53
  "tsx": "^4.22.4",
49
54
  "typescript": "^6.0.3",
50
55
  "vitest": "^4.1.9"
51
56
  },
57
+ "commitlint": {
58
+ "extends": [
59
+ "@commitlint/config-conventional"
60
+ ],
61
+ "rules": {
62
+ "type-enum": [
63
+ 2,
64
+ "always",
65
+ [
66
+ "feat",
67
+ "fix",
68
+ "perf",
69
+ "refactor",
70
+ "revert",
71
+ "docs",
72
+ "chore",
73
+ "build",
74
+ "ci",
75
+ "style",
76
+ "test"
77
+ ]
78
+ ]
79
+ }
80
+ },
52
81
  "scripts": {
53
82
  "start": "tsx ./src/create-cella-cli.ts",
54
- "try": "tsx ./src/create-cella-cli.ts ../../../cella-try-app --template ../.. --skip-install",
83
+ "try": "tsx ./src/create-cella-cli.ts ../cella-try-app",
55
84
  "clean": "rimraf ./dist",
56
85
  "build": "tsup",
57
86
  "test-build": "pnpm run build && node index.js",
@@ -60,7 +89,7 @@
60
89
  "lint": "biome check .",
61
90
  "lint:fix": "biome check --write .",
62
91
  "test": "vitest run",
63
- "test:release": "pnpm run build && vitest run tests/release-smoke.test.ts",
92
+ "test:release": "pnpm run build && vitest run --config vitest.release.config.ts",
64
93
  "test:watch": "vitest"
65
94
  }
66
95
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 CellaJS
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.