@cellajs/create-cella 0.2.2 → 0.2.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
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.
@@ -14,6 +14,10 @@ import type { ConfigMode, RequiredConfig, S3ConfigInput } from '../src/config-bu
14
14
  // Re-export for external consumers
15
15
  export { roles, hierarchy } from './hierarchy-config';
16
16
 
17
+ // Set these early for reuse
18
+ const entityTypes = ['user', 'organization', 'attachment', 'page'] as const;
19
+ const productEntityTypes = ['attachment', 'page'] as const;
20
+
17
21
  export const config = {
18
22
 
19
23
  /******************************************************************************
@@ -21,13 +25,13 @@ export const config = {
21
25
  ******************************************************************************/
22
26
 
23
27
  /** All entity types in the app - must match hierarchy.allTypes. */
24
- entityTypes: ['user', 'organization', 'attachment', 'page'] as const,
28
+ entityTypes,
25
29
 
26
30
  /** Context entities with memberships - must match hierarchy.contextTypes. */
27
31
  contextEntityTypes: ['organization'] as const,
28
32
 
29
33
  /** Product/content entities - must match hierarchy.productTypes. */
30
- productEntityTypes: ['attachment', 'page'] as const,
34
+ productEntityTypes,
31
35
 
32
36
  /**
33
37
  * Product entity types tracked for seen/unseen counts.
@@ -53,7 +57,11 @@ export const config = {
53
57
  * Entity embeddings: declares which entities are embedded as ID arrays inside
54
58
  * other entities. Forks extend when adding new embedding relationships.
55
59
  */
56
- entityEmbeddings: [] as readonly { readonly embeddedEntity: string; readonly hostEntity: string; readonly hostColumn: string }[],
60
+ entityEmbeddings: [] as readonly {
61
+ readonly embeddedEntity: (typeof productEntityTypes)[number];
62
+ readonly hostEntity: (typeof productEntityTypes)[number];
63
+ readonly hostColumn: string;
64
+ }[],
57
65
 
58
66
  /**
59
67
  * User menu structure of context entities with optional nested subentities.
@@ -111,6 +119,22 @@ export const config = {
111
119
  yjsUrl: 'wss://yjs.__project_slug__.example.com',
112
120
  /** AI service base URL */
113
121
  aiUrl: 'https://ai.__project_slug__.example.com',
122
+ /**
123
+ * Per-service toggles and public URLs. `enabled` controls whether the service
124
+ * is wired up; `publicUrl` is the externally reachable endpoint.
125
+ */
126
+ services: {
127
+ frontend: { enabled: true as boolean, publicUrl: 'https://__project_slug__.example.com' },
128
+ backend: { enabled: true as boolean, publicUrl: 'https://api.__project_slug__.example.com' },
129
+ cdc: { enabled: true as boolean },
130
+ yjs: { enabled: false as boolean, publicUrl: 'wss://yjs.__project_slug__.example.com' },
131
+ ai: { enabled: false as boolean, publicUrl: 'https://ai.__project_slug__.example.com' },
132
+ },
133
+
134
+ // Cost escape hatch: when true the backend (MODE=api) also boots every enabled
135
+ // service in-process — one VM for previews/small forks. Default false keeps the
136
+ // split (one service per process). cdc co-hosting forfeits API blue-green.
137
+ singleVM: false as boolean,
114
138
 
115
139
  /** About page URL */
116
140
  aboutUrl: '/about',
@@ -145,6 +169,8 @@ export const config = {
145
169
  maintenance: false,
146
170
  /** Cookie version - increment when changing cookie structure to invalidate old cookies */
147
171
  cookieVersion: 'v1',
172
+ /** Persisted client query-cache shape - bump on a breaking change to a cached entity so clients wipe stale cache. See info/SCHEMA_EVOLUTION.md */
173
+ clientCacheVersion: 'v1',
148
174
 
149
175
  /******************************************************************************
150
176
  * FEATURE FLAGS
@@ -165,10 +191,6 @@ export const config = {
165
191
  uploadEnabled: false as boolean,
166
192
  /** Customer support chat widget (Gleap). Unrelated to the AI module. */
167
193
  chatSupport: false as boolean,
168
- /** Yjs realtime collaboration */
169
- yjs: false as boolean,
170
- /** AI capability layer: tool registry + model runner + MCP endpoint. */
171
- ai: false as boolean,
172
194
  },
173
195
 
174
196
  /******************************************************************************
@@ -281,6 +303,8 @@ export const config = {
281
303
  googleMapsKey: '',
282
304
  /** Matrix homeserver URL for chat integration */
283
305
  matrixURL: 'https://matrix-client.matrix.org',
306
+ /** Maple.dev public ingest key, safe to embed in the frontend bundle (empty disables frontend export) */
307
+ maplePublicIngestKey: '',
284
308
 
285
309
  /******************************************************************************
286
310
  * THEMING & UI
@@ -354,7 +378,7 @@ export const config = {
354
378
  bankAccount: '',
355
379
  googleMapsUrl: '',
356
380
  scheduleCallUrl: '',
357
- blueskyUrl: '',
381
+ socialUrl: '',
358
382
  blueskyHandle: '',
359
383
  element: '',
360
384
  githubUrl: '',
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { existsSync as existsSync2 } from "fs";
5
5
  import { basename as basename2, resolve as resolve3 } from "path";
6
6
  import { input, select } from "@inquirer/prompts";
7
- import pc5 from "picocolors";
7
+ import pc6 from "picocolors";
8
8
 
9
9
  // src/constants.ts
10
10
  import pc from "picocolors";
@@ -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.2",
15
+ version: "0.2.3",
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.",
@@ -45,6 +45,7 @@ var package_default = {
45
45
  },
46
46
  scripts: {
47
47
  start: "tsx ./src/create-cella-cli.ts",
48
+ try: "tsx ./src/create-cella-cli.ts ../../../cella-try-app --template ../.. --skip-install",
48
49
  clean: "rimraf ./dist",
49
50
  build: "tsup",
50
51
  "test-build": "pnpm run build && node index.js",
@@ -54,26 +55,26 @@ var package_default = {
54
55
  lint: "biome check .",
55
56
  "lint:fix": "biome check --write .",
56
57
  test: "vitest run",
57
- "test:release": "pnpm run build && vitest run tests/release-artifact.test.ts tests/release-smoke.test.ts",
58
+ "test:release": "pnpm run build && vitest run tests/release-smoke.test.ts",
58
59
  "test:watch": "vitest"
59
60
  },
60
61
  dependencies: {
61
62
  "@inquirer/prompts": "^8.5.2",
62
- axios: "^1.17.0",
63
63
  commander: "^15.0.0",
64
- giget: "^3.1.2",
64
+ giget: "^3.3.0",
65
65
  "nano-spawn": "^2.0.0",
66
- ora: "^9.3.0",
66
+ ora: "^9.4.1",
67
67
  picocolors: "^1.1.1",
68
68
  "validate-npm-package-name": "^8.0.0"
69
69
  },
70
70
  devDependencies: {
71
- "@types/node": "catalog:",
72
- "@typescript/native-preview": "catalog:",
71
+ "@types/node": "24.10.13",
73
72
  "@types/validate-npm-package-name": "^4.0.2",
74
- tsup: "catalog:",
75
- tsx: "catalog:",
76
- vitest: "catalog:"
73
+ "@typescript/native-preview": "7.0.0-dev.20260628.1",
74
+ tsup: "^8.5.1",
75
+ tsx: "^4.22.4",
76
+ typescript: "^6.0.3",
77
+ vitest: "^4.1.9"
77
78
  }
78
79
  };
79
80
 
@@ -84,15 +85,12 @@ var TEMPLATE_URL = "github:cellajs/cella";
84
85
  var CELLA_REMOTE_URL = "git@github.com:cellajs/cella.git";
85
86
  var DESCRIPTION = package_default.description;
86
87
  var VERSION = package_default.version;
87
- var AUTHOR = package_default.author;
88
88
  var WEBSITE = package_default.homepage;
89
- var GITHUB = package_default.repository.url;
90
89
  function getHeaderLine(templateVersion) {
91
- const leftText = `\u29C8 ${NAME} \xB7 v${VERSION} \xB7 cella v${templateVersion}`;
92
- const rightText = package_default.homepage.replace("https://", "");
93
- const left = `${pc.cyan(`\u29C8 ${NAME}`)} ${pc.dim(`\xB7 v${VERSION} \xB7 cella v${templateVersion}`)}`;
94
- const right = pc.cyan(rightText);
95
- const padding = Math.max(1, 60 - leftText.length - rightText.length);
90
+ const leftText = `\u29C8 ${NAME} \xB7 ${VERSION} \xB7 cella ${templateVersion}`;
91
+ const left = `${pc.cyan(`\u29C8 ${NAME}`)} ${pc.dim(`\xB7 ${VERSION} \xB7 cella ${templateVersion}`)}`;
92
+ const right = pc.cyan(WEBSITE);
93
+ const padding = Math.max(1, 60 - leftText.length - WEBSITE.length);
96
94
  return `${left}${" ".repeat(padding)}${right}`;
97
95
  }
98
96
  var TO_REMOVE = ["./cli/create", "./info/QUICKSTART.md"];
@@ -103,10 +101,10 @@ var TO_COPY = {
103
101
  };
104
102
  var PLACEHOLDER_CONFIG = "configs/default-config.ts.template";
105
103
  async function generateEnvFromExample(examplePath, replacements) {
106
- const { readFile: readFile2 } = await import("fs/promises");
104
+ const { readFile: readFile3 } = await import("fs/promises");
107
105
  let content;
108
106
  try {
109
- content = await readFile2(examplePath, "utf8");
107
+ content = await readFile3(examplePath, "utf8");
110
108
  } catch {
111
109
  return null;
112
110
  }
@@ -190,10 +188,13 @@ ${nameEntry}${body}
190
188
  }
191
189
 
192
190
  // src/create.ts
191
+ import { execFileSync } from "child_process";
193
192
  import { existsSync } from "fs";
194
- import { cp, mkdir } from "fs/promises";
195
- import { join, relative, resolve as resolve2 } from "path";
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";
195
+ import { checkbox } from "@inquirer/prompts";
196
196
  import { downloadTemplate } from "giget";
197
+ import pc5 from "picocolors";
197
198
 
198
199
  // src/utils/git/command.ts
199
200
  import { execFile } from "child_process";
@@ -218,12 +219,6 @@ async function gitAddAll(repoPath) {
218
219
  async function gitCommit(repoPath, message) {
219
220
  return runGitCommand(["commit", "--quiet", "--no-verify", "-m", message], repoPath);
220
221
  }
221
- async function gitBranch(repoPath, branchName) {
222
- return runGitCommand(["branch", branchName], repoPath);
223
- }
224
- async function gitCheckout(repoPath, branchName) {
225
- return runGitCommand(["checkout", branchName], repoPath);
226
- }
227
222
  async function gitRemoteGetUrl(repoPath, remoteName) {
228
223
  return runGitCommand(["remote", "get-url", remoteName], repoPath);
229
224
  }
@@ -290,9 +285,8 @@ function parsePortOffset(value) {
290
285
  return parsed;
291
286
  }
292
287
  var directory = null;
293
- var newBranchName = null;
294
288
  var packageManager = "pnpm";
295
- 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) => {
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) => {
296
290
  const trimmedName = typeof name === "string" ? name.trim() : name;
297
291
  if (trimmedName) {
298
292
  const validation = validateProjectName(basename(resolve(trimmedName)));
@@ -308,7 +302,6 @@ function runCli() {
308
302
  options,
309
303
  args: command.args,
310
304
  directory,
311
- newBranchName,
312
305
  packageManager
313
306
  };
314
307
  }
@@ -332,24 +325,26 @@ function showWelcome(templateVersion) {
332
325
  console.info(getHeaderLine(templateVersion));
333
326
  console.info(DIVIDER);
334
327
  }
328
+ function confirmChoice(label, value) {
329
+ console.info(`${pc2.green("\u2713")} ${label} \xB7 ${value}`);
330
+ console.info();
331
+ }
335
332
  function showSuccess(projectName, _targetFolder, relativePath, needsCd, packageManager2) {
333
+ console.info();
334
+ console.info(pc2.bold("Next steps"));
336
335
  console.info(DIVIDER);
337
336
  console.info();
338
337
  if (needsCd) {
339
- console.info(`${pc2.green("\u2192")} cd ${pc2.cyan(relativePath)}`);
340
- console.info();
338
+ console.info(`${pc2.dim("$")} cd ${pc2.cyan(relativePath)}`);
341
339
  }
342
- console.info(`${pc2.green("\u2192")} ${pc2.cyan(`${packageManager2} quick`)} ${pc2.gray("(pglite, no docker)")}`);
343
- console.info();
344
- console.info(pc2.gray("or, for full setup:"));
345
- console.info();
346
340
  console.info(
347
- `${pc2.green("\u2192")} ${pc2.cyan(`${packageManager2} docker`)} ${pc2.dim("&&")} ${pc2.cyan(`${packageManager2} seed`)} ${pc2.dim("&&")} ${pc2.cyan(`${packageManager2} dev`)}`
341
+ `${pc2.dim("$")} ${pc2.cyan(`${packageManager2} docker`)} ${pc2.dim("&&")} ${pc2.cyan(`${packageManager2} seed`)} ${pc2.dim("&&")} ${pc2.cyan(`${packageManager2} dev`)}`
348
342
  );
349
343
  console.info();
350
- console.info(`sign in: ${pc2.gray("admin-test@cellajs.com / 12345678")}`);
344
+ const readmePath = relativePath ? `${relativePath}/README.md` : "README.md";
345
+ console.info(`${pc2.gray("To get started building, please read:")} ${pc2.cyan(readmePath)}`);
351
346
  console.info();
352
- console.info(`enjoy building ${pc2.green(projectName)} with cella!`);
347
+ console.info(`\u{1F973} enjoy building ${pc2.green(projectName)} with cella!`);
353
348
  console.info();
354
349
  }
355
350
 
@@ -477,6 +472,16 @@ ${warningMark} Placeholder config "${src}" not found > Skip`);
477
472
  import ora from "ora";
478
473
  import pc4 from "picocolors";
479
474
  var activeSpinner = null;
475
+ function pauseSpinner() {
476
+ if (activeSpinner) {
477
+ activeSpinner.stop();
478
+ }
479
+ }
480
+ function resumeSpinner() {
481
+ if (activeSpinner) {
482
+ activeSpinner.start();
483
+ }
484
+ }
480
485
  function createProgress(title, silent = false) {
481
486
  const completedSteps = [];
482
487
  const spinner = ora({
@@ -515,22 +520,6 @@ function createProgress(title, silent = false) {
515
520
  spinner.stop();
516
521
  activeSpinner = null;
517
522
  log(`${pc4.red("\u2717")} ${pc4.red(message)}`);
518
- },
519
- wrap: async (fn) => {
520
- try {
521
- return await fn();
522
- } catch (error) {
523
- const errorMessage = error instanceof Error ? error.message : String(error);
524
- spinner.stop();
525
- activeSpinner = null;
526
- log(pc4.cyan(`
527
- ${title}`));
528
- for (const step of completedSteps) {
529
- log(pc4.gray(` \u251C\u2500 ${step}`));
530
- }
531
- log(pc4.red(` \u2514\u2500 \u2717 ${errorMessage}`));
532
- throw error;
533
- }
534
523
  }
535
524
  };
536
525
  }
@@ -559,6 +548,46 @@ async function generate(packageManager2) {
559
548
  });
560
549
  }
561
550
 
551
+ // src/utils/scan-optional-modules.ts
552
+ import { readdir, readFile } from "fs/promises";
553
+ import { dirname, join, relative } from "path";
554
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".turbo", "coverage"]);
555
+ async function scanOptionalModules(targetFolder) {
556
+ const files = [];
557
+ await collectModuleFiles(targetFolder, files);
558
+ const modules = [];
559
+ for (const file of files) {
560
+ const src = await readFile(file, "utf8");
561
+ if (!/optional:\s*true/.test(src)) continue;
562
+ const name = src.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
563
+ const description = src.match(/description:\s*['"]([^'"]+)['"]/)?.[1] ?? "";
564
+ if (!name) continue;
565
+ modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });
566
+ }
567
+ return modules.sort((a, b) => a.name.localeCompare(b.name));
568
+ }
569
+ function optionalModuleFolders({ name, folder }) {
570
+ return [
571
+ folder,
572
+ `frontend/src/routes/_public/_${name}`,
573
+ `frontend/src/routes/_public/${name}`,
574
+ `frontend/src/routes/_app/_${name}`,
575
+ `frontend/src/routes/_app/${name}`,
576
+ `frontend/public/static/${name}`
577
+ ];
578
+ }
579
+ async function collectModuleFiles(dir, out) {
580
+ const entries = await readdir(dir, { withFileTypes: true });
581
+ for (const entry of entries) {
582
+ if (entry.isDirectory()) {
583
+ if (SKIP_DIRS.has(entry.name)) continue;
584
+ await collectModuleFiles(join(dir, entry.name), out);
585
+ } else if (entry.name.endsWith("-module.ts")) {
586
+ out.push(join(dir, entry.name));
587
+ }
588
+ }
589
+ }
590
+
562
591
  // src/create.ts
563
592
  function isLocalPath(path2) {
564
593
  return path2.startsWith("/") || path2.startsWith("./") || path2.startsWith("../");
@@ -569,11 +598,12 @@ function shouldSkipStep(name) {
569
598
  async function create({
570
599
  projectName,
571
600
  targetFolder,
572
- newBranchName: newBranchName2,
573
601
  packageManager: packageManager2,
574
602
  templateUrl,
603
+ templateRef,
575
604
  portOffset,
576
605
  adminEmail,
606
+ skipInstall = false,
577
607
  silent = false
578
608
  }) {
579
609
  const originalCwd = process.cwd();
@@ -587,78 +617,121 @@ async function create({
587
617
  if (isLocalTemplate) {
588
618
  progress.step("copying local template");
589
619
  const sourcePath = resolve2(originalCwd, templateUrl);
590
- if (targetFolder.startsWith(sourcePath)) {
620
+ const rel = relative2(sourcePath, targetFolder);
621
+ const targetInsideTemplate = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
622
+ if (targetInsideTemplate) {
591
623
  throw new Error(
592
624
  `Cannot create project inside the template directory.
593
625
  Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`
594
626
  );
595
627
  }
596
- await cp(sourcePath, targetFolder, {
597
- recursive: true,
598
- filter: (src) => !src.includes("node_modules") && !src.includes(".git")
599
- });
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
+ });
648
+ }
600
649
  } else {
601
- progress.step("downloading cella template");
602
- await downloadTemplate(template, {
650
+ const downloadId = templateRef ? `${template}#${templateRef}` : template;
651
+ progress.step(`downloading cella template${templateRef ? ` (${templateRef})` : ""}`);
652
+ await downloadTemplate(downloadId, {
603
653
  cwd: process.cwd(),
604
654
  dir: targetFolder,
605
655
  force: true,
606
656
  provider: "github"
607
657
  });
608
658
  }
659
+ if (!silent) await promptOptionalModules(targetFolder);
609
660
  progress.step("cleaning template");
610
661
  const displayName = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
611
662
  await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });
612
- if (!shouldSkipStep("install")) {
663
+ if (!skipInstall && !shouldSkipStep("install")) {
613
664
  progress.step("installing dependencies");
614
665
  await install(packageManager2);
615
666
  }
616
- if (!shouldSkipStep("generate")) {
667
+ if (!skipInstall && !shouldSkipStep("generate")) {
617
668
  progress.step("generating migrations");
618
669
  await generate(packageManager2);
619
670
  }
620
- const gitFolderPath = join(targetFolder, ".git");
671
+ const gitFolderPath = join2(targetFolder, ".git");
621
672
  if (!shouldSkipStep("git") && !existsSync(gitFolderPath)) {
622
673
  progress.step("initializing git");
623
674
  await gitInit(targetFolder);
624
675
  await gitAddAll(targetFolder);
625
676
  await gitCommit(targetFolder, "Initial commit");
626
- if (newBranchName2) {
627
- progress.step(`creating branch '${newBranchName2}'`);
628
- await gitBranch(targetFolder, newBranchName2);
629
- await gitCheckout(targetFolder, newBranchName2);
630
- }
631
677
  }
632
678
  if (!shouldSkipStep("remote")) {
633
679
  progress.step("adding upstream remote");
634
680
  await addRemote({ targetFolder, silent: true });
635
681
  }
636
- progress.done(`created ${projectName}`);
682
+ progress.done(pc5.bold(`created ${projectName}`));
637
683
  } catch (error) {
638
684
  progress.fail(error instanceof Error ? error.message : String(error));
639
- process.exit(1);
685
+ throw error;
640
686
  }
641
687
  const needsCd = originalCwd !== targetFolder;
642
- const relativePath = relative(originalCwd, targetFolder);
688
+ const relativePath = relative2(originalCwd, targetFolder);
643
689
  showSuccess(projectName, targetFolder, relativePath, needsCd, packageManager2);
644
690
  }
691
+ async function promptOptionalModules(targetFolder) {
692
+ const modules = await scanOptionalModules(targetFolder);
693
+ if (modules.length === 0) return;
694
+ if (!process.stdin.isTTY) return;
695
+ pauseSpinner();
696
+ console.info();
697
+ const keep = await checkbox(
698
+ {
699
+ message: "Optional modules to include",
700
+ // Leading space on the radio icons adds a gap after the cursor (inquirer renders
701
+ // `${cursor}${checkbox} ${name}` with no space between cursor and checkbox).
702
+ theme: { icon: { checked: ` ${pc5.green("\u25C9")}`, unchecked: " \u25EF" } },
703
+ choices: modules.map((m) => ({
704
+ name: `${m.name} ${pc5.dim(`\xB7 ${m.description}`)}`,
705
+ value: m.name,
706
+ checked: true
707
+ }))
708
+ },
709
+ // Clear the answered prompt so we can print a compact, non-bold summary that lists
710
+ // only the kept module names (not their descriptions).
711
+ { clearPromptOnDone: true }
712
+ );
713
+ confirmChoice("Optional modules", keep.length ? keep.join(", ") : pc5.dim("none"));
714
+ resumeSpinner();
715
+ 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 })));
717
+ }
645
718
 
646
719
  // src/utils/detect-used-ports.ts
647
- import { readdir, readFile } from "fs/promises";
648
- import { dirname, join as join2 } from "path";
720
+ import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
721
+ import { dirname as dirname3, join as join3 } from "path";
649
722
  async function detectUsedPorts(targetFolder) {
650
- const parentDir = dirname(targetFolder);
723
+ const parentDir = dirname3(targetFolder);
651
724
  const used = [];
652
725
  let siblings;
653
726
  try {
654
- siblings = await readdir(parentDir);
727
+ siblings = await readdir2(parentDir);
655
728
  } catch {
656
729
  return used;
657
730
  }
658
731
  for (const name of siblings) {
659
- const configPath = join2(parentDir, name, "shared/config/config.development.ts");
732
+ const configPath = join3(parentDir, name, "shared/config/config.development.ts");
660
733
  try {
661
- const content = await readFile(configPath, "utf8");
734
+ const content = await readFile2(configPath, "utf8");
662
735
  const feMatch = content.match(/frontendUrl:\s*'http:\/\/localhost:(\d+)'/);
663
736
  const beMatch = content.match(/backendUrl:\s*'http:\/\/localhost:(\d+)'/);
664
737
  if (feMatch && beMatch) {
@@ -685,23 +758,63 @@ function findNextOffset(usedPorts) {
685
758
  }
686
759
 
687
760
  // src/utils/extract-package-json-version-from-uri.ts
688
- import axios from "axios";
689
761
  async function extractPackageJsonVersionFromUri(repositoryUrl, branch = "main") {
690
762
  const [owner, repo] = repositoryUrl.replace("github:", "").split("/");
691
- const packageJsonUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/package.json`;
763
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${branch}`;
692
764
  try {
693
- const response = await axios.get(packageJsonUrl);
694
- const packageJson = response.data;
765
+ const response = await fetch(apiUrl, {
766
+ headers: { Accept: "application/vnd.github.raw+json" }
767
+ });
768
+ if (!response.ok) return "unknown";
769
+ const packageJson = await response.json();
695
770
  return packageJson.version || "unknown";
696
- } catch (error) {
771
+ } catch {
697
772
  return "unknown";
698
773
  }
699
774
  }
700
775
 
776
+ // src/utils/fetch-template-metadata.ts
777
+ function parseRepo(repositoryUrl) {
778
+ const [owner, repo] = repositoryUrl.replace("github:", "").split("/");
779
+ return [owner, repo];
780
+ }
781
+ function toDate(iso) {
782
+ return iso ? iso.slice(0, 10) : "unknown";
783
+ }
784
+ var GITHUB_HEADERS = { Accept: "application/vnd.github+json" };
785
+ async function fetchLatestRelease(repositoryUrl) {
786
+ const [owner, repo] = parseRepo(repositoryUrl);
787
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=30`;
788
+ try {
789
+ const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });
790
+ if (!response.ok) return null;
791
+ const releases = await response.json();
792
+ const release = releases.find((r) => !r.draft && !r.prerelease && /^v\d/.test(r.tag_name ?? ""));
793
+ if (!release?.tag_name) return null;
794
+ return { tag: release.tag_name, date: toDate(release.published_at) };
795
+ } catch {
796
+ return null;
797
+ }
798
+ }
799
+ async function fetchLatestCommit(repositoryUrl, branch = "main") {
800
+ const [owner, repo] = parseRepo(repositoryUrl);
801
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`;
802
+ try {
803
+ const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });
804
+ if (!response.ok) return null;
805
+ const data = await response.json();
806
+ if (!data.sha) return null;
807
+ const message = (data.commit?.message ?? "").split("\n")[0];
808
+ return { sha: data.sha, shortSha: data.sha.slice(0, 7), message, date: toDate(data.commit?.author?.date) };
809
+ } catch {
810
+ return null;
811
+ }
812
+ }
813
+
701
814
  // src/utils/is-empty-directory.ts
702
- import { readdir as readdir2 } from "fs/promises";
815
+ import { readdir as readdir3 } from "fs/promises";
703
816
  async function isEmptyDirectory(path2) {
704
- const files = await readdir2(path2);
817
+ const files = await readdir3(path2);
705
818
  return files.length === 0 || files.length === 1 && files[0] === ".git";
706
819
  }
707
820
 
@@ -711,6 +824,7 @@ async function main() {
711
824
  showWelcome(templateVersion);
712
825
  const promptTheme = { prefix: "", style: { answer: (text) => text } };
713
826
  const promptContext = { clearPromptOnDone: true };
827
+ const templateRef = cli.options.template ? void 0 : await promptTemplateRef(promptTheme, promptContext);
714
828
  if (!cli.directory) {
715
829
  cli.directory = await input(
716
830
  {
@@ -725,9 +839,6 @@ async function main() {
725
839
  promptContext
726
840
  );
727
841
  }
728
- if (!cli.newBranchName) {
729
- cli.newBranchName = "development";
730
- }
731
842
  const targetFolder = resolve3(cli.directory);
732
843
  const projectName = basename2(targetFolder)?.toLowerCase() || "project";
733
844
  if (existsSync2(targetFolder) && !await isEmptyDirectory(targetFolder)) {
@@ -749,44 +860,85 @@ async function main() {
749
860
  }
750
861
  }
751
862
  const portOffset = cli.options.portOffset ?? await promptPortOffset(targetFolder, promptTheme, promptContext);
863
+ confirmChoice("Port offset", formatOffset(portOffset));
752
864
  const adminEmail = cli.options.adminEmail ?? await input(
753
865
  {
754
866
  message: "Admin email for initial seed user",
755
- default: `admin@${projectName}.com`,
867
+ default: "admin@example.com",
756
868
  theme: promptTheme
757
869
  },
758
870
  promptContext
759
871
  );
872
+ confirmChoice("Admin email", adminEmail);
760
873
  const createOptions = {
761
874
  projectName,
762
875
  targetFolder,
763
- newBranchName: cli.newBranchName,
764
876
  packageManager: cli.packageManager,
765
877
  templateUrl: cli.options.template,
878
+ templateRef,
766
879
  portOffset,
767
- adminEmail
880
+ adminEmail,
881
+ skipInstall: cli.options.skipInstall
768
882
  };
769
883
  await create(createOptions);
770
884
  }
771
- main().catch(console.error);
885
+ main().catch((error) => {
886
+ console.error(error);
887
+ process.exit(1);
888
+ });
889
+ async function promptTemplateRef(theme, context) {
890
+ const [release, commit] = await Promise.all([fetchLatestRelease(TEMPLATE_URL), fetchLatestCommit(TEMPLATE_URL)]);
891
+ if (!release && !commit) return void 0;
892
+ console.info(pc6.dim("You are about to install the Cella template."));
893
+ console.info();
894
+ if (release) console.info(` Latest release: ${release.tag} ${pc6.dim(`\xB7 ${release.date}`)}`);
895
+ if (commit) {
896
+ console.info(` Latest commit: ${commit.shortSha} ${pc6.dim(`\xB7 ${commit.message} \xB7 ${commit.date}`)}`);
897
+ const repo = TEMPLATE_URL.replace("github:", "");
898
+ console.info(` ${pc6.gray(`https://github.com/${repo}/commit/${commit.sha}`)}`);
899
+ }
900
+ console.info();
901
+ if (!release) return commit?.sha;
902
+ if (!commit) return release.tag;
903
+ const choice = await select(
904
+ {
905
+ message: "Start from",
906
+ theme,
907
+ choices: [
908
+ { name: "Latest release", value: "release" },
909
+ { name: "Latest commit", value: "commit" }
910
+ ]
911
+ },
912
+ context
913
+ );
914
+ if (choice === "release") {
915
+ confirmChoice("Latest release", release.tag);
916
+ return release.tag;
917
+ }
918
+ confirmChoice("Latest commit", commit.shortSha);
919
+ return commit.sha;
920
+ }
772
921
  function formatOffset(o, suffix = "") {
773
922
  return `${o} \u2192 :${3e3 + o} / :${4e3 + o} / :${5432 + o}${suffix}`;
774
923
  }
775
924
  async function promptPortOffset(targetFolder, theme, context) {
776
925
  const usedPorts = await detectUsedPorts(targetFolder);
777
926
  const suggested = findNextOffset(usedPorts);
927
+ let message = `${pc6.bold("Port offset")} ${pc6.dim("\xB7 to avoid conflicts with other forks/clones")}`;
778
928
  if (usedPorts.length > 0) {
779
- console.info(pc5.dim("\nDetected cella forks in sibling directories:"));
780
- for (const p of usedPorts) {
781
- console.info(pc5.dim(` ${p.project}: frontend :${p.frontend}, backend :${p.backend} (offset ${p.offset})`));
782
- }
783
- console.info();
929
+ const count = usedPorts.length;
930
+ message += pc6.dim(`
931
+ Detected ${count} sibling fork${count === 1 ? "" : "s"} \xB7 suggested offset ${suggested}`);
784
932
  }
785
- const presets = [0, 10, 20, 30].filter((o) => o !== suggested);
933
+ const portTheme = {
934
+ ...theme,
935
+ style: { ...theme.style ?? {}, message: (text) => text }
936
+ };
937
+ const presets = [10, 20, 30].filter((o) => o !== suggested);
786
938
  const choice = await select(
787
939
  {
788
- message: "Port offset (avoids conflicts with sibling forks)",
789
- theme,
940
+ message,
941
+ theme: portTheme,
790
942
  choices: [
791
943
  ...suggested > 0 ? [{ name: formatOffset(suggested, " (suggested)"), value: suggested }] : [],
792
944
  { name: formatOffset(0, " (default)"), value: 0 },
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/detect-used-ports.ts","../src/utils/extract-package-json-version-from-uri.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, showWelcome } from '#/modules/cli';\nimport { detectUsedPorts, findNextOffset } from '#/utils/detect-used-ports';\nimport { extractPackageJsonVersionFromUri } from '#/utils/extract-package-json-version-from-uri';\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 // 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 // Default to creating a 'development' working branch\n if (!cli.newBranchName) {\n cli.newBranchName = 'development';\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\n // Prompt for admin email\n const adminEmail =\n cli.options.adminEmail ??\n (await input(\n {\n message: 'Admin email for initial seed user',\n default: `admin@${projectName}.com`,\n theme: promptTheme,\n },\n promptContext,\n ));\n\n // Proceed with the project creation\n const createOptions: CreateOptions = {\n projectName,\n targetFolder,\n newBranchName: cli.newBranchName,\n packageManager: cli.packageManager,\n templateUrl: cli.options.template,\n portOffset,\n adminEmail,\n };\n\n await create(createOptions);\n}\n\nmain().catch(console.error);\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 if (usedPorts.length > 0) {\n console.info(pc.dim('\\nDetected cella forks in sibling directories:'));\n for (const p of usedPorts) {\n console.info(pc.dim(` ${p.project}: frontend :${p.frontend}, backend :${p.backend} (offset ${p.offset})`));\n }\n console.info();\n }\n\n const presets = [0, 10, 20, 30].filter((o) => o !== suggested);\n const choice = await select(\n {\n message: 'Port offset (avoids conflicts with sibling forks)',\n theme,\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 AUTHOR: string = packageJson.author;\nexport const WEBSITE: string = packageJson.homepage;\nexport const GITHUB: string = packageJson.repository.url;\n\nexport function getHeaderLine(templateVersion?: string): string {\n const leftText = `⧈ ${NAME} · v${VERSION} · cella v${templateVersion}`;\n const rightText = packageJson.homepage.replace('https://', '');\n const left = `${pc.cyan(`⧈ ${NAME}`)} ${pc.dim(`· v${VERSION} · cella v${templateVersion}`)}`;\n const right = pc.cyan(rightText);\n const padding = Math.max(1, 60 - leftText.length - rightText.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.2\",\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 \"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-artifact.test.ts tests/release-smoke.test.ts\",\n \"test:watch\": \"vitest\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^8.5.2\",\n \"axios\": \"^1.17.0\",\n \"commander\": \"^15.0.0\",\n \"giget\": \"^3.1.2\",\n \"nano-spawn\": \"^2.0.0\",\n \"ora\": \"^9.3.0\",\n \"picocolors\": \"^1.1.1\",\n \"validate-npm-package-name\": \"^8.0.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"catalog:\",\n \"@typescript/native-preview\": \"catalog:\",\n \"@types/validate-npm-package-name\": \"^4.0.2\",\n \"tsup\": \"catalog:\",\n \"tsx\": \"catalog:\",\n \"vitest\": \"catalog:\"\n }\n}\n","import { existsSync } from 'node:fs';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { join, relative, resolve } from 'node:path';\nimport { downloadTemplate } from 'giget';\nimport { addRemote } from '#/add-remote';\nimport { TEMPLATE_URL } from '#/constants';\nimport { type CreateOptions, showSuccess } from '#/modules/cli';\nimport { cleanTemplate } from '#/utils/clean-template';\nimport { gitAddAll, gitBranch, gitCheckout, gitCommit, gitInit } from '#/utils/git';\nimport { createProgress } from '#/utils/progress';\nimport { generate, install } from '#/utils/run-package-manager-command';\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 newBranchName,\n packageManager,\n templateUrl,\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)\n if (targetFolder.startsWith(sourcePath)) {\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 await cp(sourcePath, targetFolder, {\n recursive: true,\n filter: (src) => !src.includes('node_modules') && !src.includes('.git'),\n });\n } else {\n progress.step('downloading cella template');\n await downloadTemplate(template, {\n cwd: process.cwd(),\n dir: targetFolder,\n force: true,\n provider: 'github',\n });\n }\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\n if (!shouldSkipStep('install')) {\n progress.step('installing dependencies');\n await install(packageManager);\n }\n\n // Generate SQL files\n if (!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 if (newBranchName) {\n progress.step(`creating branch '${newBranchName}'`);\n await gitBranch(targetFolder, newBranchName);\n await gitCheckout(targetFolder, newBranchName);\n }\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(`created ${projectName}`);\n } catch (error) {\n progress.fail(error instanceof Error ? error.message : String(error));\n process.exit(1);\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","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 * Creates a new branch with the specified name.\n */\nexport async function gitBranch(repoPath: string, branchName: string): Promise<string> {\n return runGitCommand(['branch', branchName], repoPath);\n}\n\n/**\n * Checks out the specified branch.\n */\nexport async function gitCheckout(repoPath: string, branchName: string): Promise<string> {\n return runGitCommand(['checkout', branchName], 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 newBranchName: 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 newBranchName,\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 * 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(DIVIDER);\n console.info();\n\n // Navigation instruction\n if (needsCd) {\n console.info(`${pc.green('→')} cd ${pc.cyan(relativePath)}`);\n console.info();\n }\n\n // Quick start options\n console.info(`${pc.green('→')} ${pc.cyan(`${packageManager} quick`)} ${pc.gray('(pglite, no docker)')}`);\n console.info();\n console.info(pc.gray('or, for full setup:'));\n console.info();\n console.info(\n `${pc.green('→')} ${pc.cyan(`${packageManager} docker`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} seed`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} dev`)}`,\n );\n console.info();\n\n // Credentials\n console.info(`sign in: ${pc.gray('admin-test@cellajs.com / 12345678')}`);\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 */\nexport async 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 */\nexport async 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 */\nexport async 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 /** Wrap an async operation to auto-fail on error */\n wrap: <T>(fn: () => Promise<T>) => Promise<T>;\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 wrap: async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n // Stop spinner and show tree-style breakdown on failure\n spinner.stop();\n activeSpinner = null;\n log(pc.cyan(`\\n${title}`));\n for (const step of completedSteps) {\n log(pc.gray(` ├─ ${step}`));\n }\n log(pc.red(` └─ ✗ ${errorMessage}`));\n throw error;\n }\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 } 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","import axios from 'axios';\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 // Construct the URL for the package.json file in the provided branch\n const packageJsonUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/package.json`;\n\n try {\n // Fetch the package.json file\n const response = await axios.get(packageJsonUrl);\n const packageJson = response.data;\n\n // Return the version from the package.json, or 'unknown' if not found\n return packageJson.version || 'unknown';\n } catch (error) {\n // If there's an error (file not found, etc.), return 'unknown'\n return 'unknown';\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","resolve","options","pc","pc","packageManager","pc","pc","resolve","pc","pc","packageManager","path","newBranchName","packageManager","resolve","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/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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cellajs/create-cella",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
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.",
@@ -31,36 +31,36 @@
31
31
  "bin": {
32
32
  "create-cella": "index.js"
33
33
  },
34
+ "dependencies": {
35
+ "@inquirer/prompts": "^8.5.2",
36
+ "commander": "^15.0.0",
37
+ "giget": "^3.3.0",
38
+ "nano-spawn": "^2.0.0",
39
+ "ora": "^9.4.1",
40
+ "picocolors": "^1.1.1",
41
+ "validate-npm-package-name": "^8.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "24.10.13",
45
+ "@types/validate-npm-package-name": "^4.0.2",
46
+ "@typescript/native-preview": "7.0.0-dev.20260628.1",
47
+ "tsup": "^8.5.1",
48
+ "tsx": "^4.22.4",
49
+ "typescript": "^6.0.3",
50
+ "vitest": "^4.1.9"
51
+ },
34
52
  "scripts": {
35
53
  "start": "tsx ./src/create-cella-cli.ts",
54
+ "try": "tsx ./src/create-cella-cli.ts ../../../cella-try-app --template ../.. --skip-install",
36
55
  "clean": "rimraf ./dist",
37
56
  "build": "tsup",
38
57
  "test-build": "pnpm run build && node index.js",
39
- "prepublishOnly": "pnpm run test:release",
40
58
  "check": "pnpm ts && pnpm biome check --write .",
41
59
  "ts": "tsgo --pretty",
42
60
  "lint": "biome check .",
43
61
  "lint:fix": "biome check --write .",
44
62
  "test": "vitest run",
45
- "test:release": "pnpm run build && vitest run tests/release-artifact.test.ts tests/release-smoke.test.ts",
63
+ "test:release": "pnpm run build && vitest run tests/release-smoke.test.ts",
46
64
  "test:watch": "vitest"
47
- },
48
- "dependencies": {
49
- "@inquirer/prompts": "^8.5.2",
50
- "axios": "^1.17.0",
51
- "commander": "^15.0.0",
52
- "giget": "^3.1.2",
53
- "nano-spawn": "^2.0.0",
54
- "ora": "^9.3.0",
55
- "picocolors": "^1.1.1",
56
- "validate-npm-package-name": "^8.0.0"
57
- },
58
- "devDependencies": {
59
- "@types/node": "catalog:",
60
- "@typescript/native-preview": "catalog:",
61
- "@types/validate-npm-package-name": "^4.0.2",
62
- "tsup": "catalog:",
63
- "tsx": "catalog:",
64
- "vitest": "catalog:"
65
65
  }
66
- }
66
+ }