@cellajs/create-cella 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,12 @@
1
1
  # @cellajs/create-cella
2
2
 
3
- CLI tool to scaffold a new Cella project from the template.
4
-
5
- ## Overview
6
-
7
- This CLI creates a new Cella project by downloading the latest template, scaffolding your project, and configuring git with upstream tracking for future syncs. It runs no shell commands and requires no `git` binary — git operations use [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) and the template is fetched and extracted in-process.
3
+ CLI tool to scaffold a new Cella project from [the template](https://github.com/cellajs/cella).
8
4
 
9
5
  ## Usage
10
6
 
11
7
  ```bash
12
8
  pnpm create @cellajs/cella my-app
13
9
  ```
14
-
15
- Running without arguments starts interactive mode, prompting for:
16
-
17
- 1. **Project name** – Directory name and package name
18
- 2. **New branch** – Optionally create a dev branch alongside `main`
19
- 3. **Directory conflict** – If target exists, choose to cancel or continue
20
-
21
10
  ## CLI Options
22
11
 
23
12
  ```bash
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import pc from "picocolors";
12
12
  // package.json
13
13
  var package_default = {
14
14
  name: "@cellajs/create-cella",
15
- version: "0.3.0",
15
+ version: "0.3.2",
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.",
@@ -31,7 +31,6 @@ var package_default = {
31
31
  type: "module",
32
32
  main: "./dist/index.js",
33
33
  files: [
34
- "configs",
35
34
  "dist",
36
35
  "index.js",
37
36
  "README.md"
@@ -63,7 +62,6 @@ var package_default = {
63
62
  commander: "^15.0.0",
64
63
  ignore: "^7.0.5",
65
64
  "isomorphic-git": "^1.38.5",
66
- nanotar: "^0.3.0",
67
65
  ora: "^9.4.1",
68
66
  picocolors: "^1.1.1",
69
67
  "validate-npm-package-name": "^8.0.0"
@@ -117,9 +115,9 @@ var CELLA_REMOTE_URL = "git@github.com:cellajs/cella.git";
117
115
  var DESCRIPTION = package_default.description;
118
116
  var VERSION = package_default.version;
119
117
  var WEBSITE = package_default.homepage;
120
- function getHeaderLine(templateVersion) {
121
- const leftText = `\u29C8 ${NAME} \xB7 ${VERSION} \xB7 cella ${templateVersion}`;
122
- const left = `${pc.cyan(`\u29C8 ${NAME}`)} ${pc.dim(`\xB7 ${VERSION} \xB7 cella ${templateVersion}`)}`;
118
+ function getHeaderLine() {
119
+ const leftText = `\u29C8 ${NAME} \xB7 ${VERSION}`;
120
+ const left = `${pc.cyan(`\u29C8 ${NAME}`)} ${pc.dim(`\xB7 ${VERSION}`)}`;
123
121
  const right = pc.cyan(WEBSITE);
124
122
  const padding = Math.max(1, 60 - leftText.length - WEBSITE.length);
125
123
  return `${left}${" ".repeat(padding)}${right}`;
@@ -130,7 +128,7 @@ var TO_COPY = {
130
128
  "./frontend/.env.example": "./frontend/.env",
131
129
  "./info/QUICKSTART.md": "README.md"
132
130
  };
133
- var PLACEHOLDER_CONFIG = "configs/default-config.ts.template";
131
+ var PLACEHOLDER_CONFIG = "shared/config/config.template.ts";
134
132
  async function generateEnvFromExample(examplePath, replacements) {
135
133
  const { readFile: readFile4 } = await import("fs/promises");
136
134
  let content;
@@ -220,8 +218,8 @@ ${nameEntry}${body}
220
218
 
221
219
  // src/create.ts
222
220
  import { existsSync } from "fs";
223
- import { cp, mkdir as mkdir2, rm } from "fs/promises";
224
- import { dirname as dirname3, isAbsolute, join as join4, relative as relative2, resolve as resolve2 } from "path";
221
+ import { cp, mkdir, rm as rm2 } from "fs/promises";
222
+ import { dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve2 } from "path";
225
223
  import { checkbox } from "@inquirer/prompts";
226
224
  import pc5 from "picocolors";
227
225
 
@@ -338,13 +336,13 @@ function showAscii() {
338
336
  console.info(pc2.cyan("\u2592\u2593\u2588 \u2588\u2593\u2592 | (_| __/ | | (_| | "));
339
337
  console.info(pc2.cyan("\u2592\u2593\u2588\u2588\u2588\u2588\u2588\u2593\u2592 \\___\\___|_|_|\\__,_| "));
340
338
  }
341
- function showWelcome(templateVersion) {
339
+ function showWelcome() {
342
340
  console.info();
343
341
  showAscii();
344
342
  console.info();
345
343
  console.info(pc2.dim(DESCRIPTION));
346
344
  console.info();
347
- console.info(getHeaderLine(templateVersion));
345
+ console.info(getHeaderLine());
348
346
  console.info(DIVIDER);
349
347
  }
350
348
  function confirmChoice(label, value) {
@@ -375,21 +373,8 @@ function showSuccess(projectName, _targetFolder, relativePath, needsCd, packageM
375
373
  // src/utils/clean-template.ts
376
374
  import fs2 from "fs/promises";
377
375
  import path from "path";
378
- import { fileURLToPath } from "url";
379
376
  import pc3 from "picocolors";
380
377
  var warningMark = pc3.yellow("\u26A0");
381
- async function resolvePlaceholderConfigPath() {
382
- const here = path.dirname(fileURLToPath(import.meta.url));
383
- const candidates = [path.resolve(here, "../..", PLACEHOLDER_CONFIG), path.resolve(here, "..", PLACEHOLDER_CONFIG)];
384
- for (const candidate of candidates) {
385
- try {
386
- await fs2.access(candidate);
387
- return candidate;
388
- } catch {
389
- }
390
- }
391
- return candidates[0];
392
- }
393
378
  async function cleanTemplate({
394
379
  targetFolder,
395
380
  projectName,
@@ -408,7 +393,7 @@ async function cleanTemplate({
408
393
  const destAbsolutePath = path.resolve(targetFolder, dest);
409
394
  await copyFile(srcAbsolutePath, destAbsolutePath);
410
395
  }
411
- await applyPlaceholderConfig(targetFolder, projectName);
396
+ await applyPlaceholderConfig(targetFolder, projectName, displayName);
412
397
  const backendReplacements = getBackendEnvReplacements(projectName, adminEmail, portOffset);
413
398
  const backendEnv = await generateEnvFromExample(
414
399
  path.resolve(targetFolder, "backend/.env.example"),
@@ -443,18 +428,14 @@ async function cleanTemplate({
443
428
  });
444
429
  }
445
430
  async function removeFolderContents(folderPath) {
446
- const files = await fs2.readdir(folderPath);
447
- await Promise.all(
448
- files.map(async (file) => {
449
- const filePath = path.join(folderPath, file);
450
- const stat = await fs2.lstat(filePath);
451
- if (stat.isDirectory()) {
452
- await fs2.rm(filePath, { recursive: true, force: true });
453
- } else {
454
- await fs2.rm(filePath);
455
- }
456
- })
457
- );
431
+ let files;
432
+ try {
433
+ files = await fs2.readdir(folderPath);
434
+ } catch (err) {
435
+ if (err.code === "ENOENT") return;
436
+ throw err;
437
+ }
438
+ await Promise.all(files.map((file) => fs2.rm(path.join(folderPath, file), { recursive: true, force: true })));
458
439
  }
459
440
  async function removeFileOrFolder(pathToRemove) {
460
441
  await fs2.rm(pathToRemove, { recursive: true, force: true });
@@ -473,19 +454,19 @@ ${warningMark} Source file "${src}" does not exist > Skip copy`);
473
454
  }
474
455
  }
475
456
  }
476
- async function applyPlaceholderConfig(targetFolder, projectName) {
477
- const displayName = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
478
- const src = await resolvePlaceholderConfigPath();
457
+ async function applyPlaceholderConfig(targetFolder, projectName, displayName) {
458
+ const src = path.resolve(targetFolder, PLACEHOLDER_CONFIG);
479
459
  const dest = path.resolve(targetFolder, "./shared/config/config.default.ts");
480
460
  try {
481
461
  let content = await fs2.readFile(src, "utf8");
482
462
  content = content.replaceAll("__project_name__", displayName);
483
463
  content = content.replaceAll("__project_slug__", projectName);
484
464
  await fs2.writeFile(dest, content, "utf8");
465
+ await fs2.rm(src, { force: true });
485
466
  } catch (err) {
486
467
  if (err.code === "ENOENT") {
487
468
  console.info(`
488
- ${warningMark} Placeholder config "${src}" not found > Skip`);
469
+ ${warningMark} Fork config template "${src}" not found > Skip`);
489
470
  } else {
490
471
  throw err;
491
472
  }
@@ -493,9 +474,11 @@ ${warningMark} Placeholder config "${src}" not found > Skip`);
493
474
  }
494
475
 
495
476
  // src/utils/download-template.ts
496
- import { mkdir, writeFile } from "fs/promises";
497
- import { dirname, join } from "path";
498
- import { parseTarGzip } from "nanotar";
477
+ import fs3 from "fs";
478
+ import { rm } from "fs/promises";
479
+ import { join } from "path";
480
+ import git2 from "isomorphic-git";
481
+ import http from "isomorphic-git/http/node";
499
482
  function parseGithubRepo(template) {
500
483
  const [owner, repo] = template.replace(/^github:/, "").split("/");
501
484
  if (!owner || !repo) {
@@ -505,22 +488,9 @@ function parseGithubRepo(template) {
505
488
  }
506
489
  async function downloadGithubTemplate(template, ref, targetFolder) {
507
490
  const { owner, repo } = parseGithubRepo(template);
508
- const url = `https://codeload.github.com/${owner}/${repo}/tar.gz/${ref}`;
509
- const response = await fetch(url);
510
- if (!response.ok) {
511
- throw new Error(`Failed to download template ${owner}/${repo}@${ref}: ${response.status} ${response.statusText}`);
512
- }
513
- const archive = new Uint8Array(await response.arrayBuffer());
514
- const entries = await parseTarGzip(archive);
515
- for (const entry of entries) {
516
- if (entry.type !== "file" || !entry.data) continue;
517
- const relativePath = entry.name.replace(/^[^/]+\//, "");
518
- if (!relativePath) continue;
519
- const dest = join(targetFolder, relativePath);
520
- await mkdir(dirname(dest), { recursive: true });
521
- const mode = entry.attrs?.mode ? Number.parseInt(entry.attrs.mode, 8) : void 0;
522
- await writeFile(dest, entry.data, mode ? { mode } : void 0);
523
- }
491
+ const url = `https://github.com/${owner}/${repo}.git`;
492
+ await git2.clone({ fs: fs3, http, dir: targetFolder, url, ref, singleBranch: true, depth: 1 });
493
+ await rm(join(targetFolder, ".git"), { recursive: true, force: true });
524
494
  }
525
495
 
526
496
  // src/utils/list-template-files.ts
@@ -605,7 +575,7 @@ function createProgress(title, silent = false) {
605
575
 
606
576
  // src/utils/scan-optional-modules.ts
607
577
  import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
608
- import { dirname as dirname2, join as join3, relative } from "path";
578
+ import { dirname, join as join3, relative } from "path";
609
579
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".turbo", "coverage"]);
610
580
  async function scanOptionalModules(targetFolder) {
611
581
  const files = [];
@@ -617,7 +587,7 @@ async function scanOptionalModules(targetFolder) {
617
587
  const name = src.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
618
588
  const description = src.match(/description:\s*['"]([^'"]+)['"]/)?.[1] ?? "";
619
589
  if (!name) continue;
620
- modules.push({ name, description, folder: relative(targetFolder, dirname2(file)) });
590
+ modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });
621
591
  }
622
592
  return modules.sort((a, b) => a.name.localeCompare(b.name));
623
593
  }
@@ -666,7 +636,7 @@ async function create({
666
636
  const progress = createProgress("creating project", silent);
667
637
  try {
668
638
  progress.step("creating project folder");
669
- await mkdir2(targetFolder, { recursive: true });
639
+ await mkdir(targetFolder, { recursive: true });
670
640
  process.chdir(targetFolder);
671
641
  if (isLocalTemplate) {
672
642
  progress.step("copying local template");
@@ -684,7 +654,7 @@ async function create({
684
654
  const src = join4(sourcePath, rel2);
685
655
  if (!existsSync(src)) continue;
686
656
  const dest = join4(targetFolder, rel2);
687
- await mkdir2(dirname3(dest), { recursive: true });
657
+ await mkdir(dirname2(dest), { recursive: true });
688
658
  await cp(src, dest);
689
659
  }
690
660
  } else {
@@ -741,14 +711,14 @@ async function promptOptionalModules(targetFolder) {
741
711
  confirmChoice("Optional modules", keep.length ? keep.join(", ") : pc5.dim("none"));
742
712
  resumeSpinner();
743
713
  const folders = modules.filter((m) => !keep.includes(m.name)).flatMap(optionalModuleFolders);
744
- await Promise.all(folders.map((f) => rm(join4(targetFolder, f), { recursive: true, force: true })));
714
+ await Promise.all(folders.map((f) => rm2(join4(targetFolder, f), { recursive: true, force: true })));
745
715
  }
746
716
 
747
717
  // src/utils/detect-used-ports.ts
748
718
  import { readdir as readdir3, readFile as readFile3 } from "fs/promises";
749
- import { dirname as dirname4, join as join5 } from "path";
719
+ import { dirname as dirname3, join as join5 } from "path";
750
720
  async function detectUsedPorts(targetFolder) {
751
- const parentDir = dirname4(targetFolder);
721
+ const parentDir = dirname3(targetFolder);
752
722
  const used = [];
753
723
  let siblings;
754
724
  try {
@@ -785,22 +755,6 @@ function findNextOffset(usedPorts) {
785
755
  return 0;
786
756
  }
787
757
 
788
- // src/utils/extract-package-json-version-from-uri.ts
789
- async function extractPackageJsonVersionFromUri(repositoryUrl, branch = "main") {
790
- const [owner, repo] = repositoryUrl.replace("github:", "").split("/");
791
- const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${branch}`;
792
- try {
793
- const response = await fetch(apiUrl, {
794
- headers: { Accept: "application/vnd.github.raw+json" }
795
- });
796
- if (!response.ok) return "unknown";
797
- const packageJson = await response.json();
798
- return packageJson.version || "unknown";
799
- } catch {
800
- return "unknown";
801
- }
802
- }
803
-
804
758
  // src/utils/fetch-template-metadata.ts
805
759
  function parseRepo(repositoryUrl) {
806
760
  const [owner, repo] = repositoryUrl.replace("github:", "").split("/");
@@ -817,7 +771,7 @@ async function fetchLatestRelease(repositoryUrl) {
817
771
  const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });
818
772
  if (!response.ok) return null;
819
773
  const releases = await response.json();
820
- const release = releases.find((r) => !r.draft && !r.prerelease && /^v\d/.test(r.tag_name ?? ""));
774
+ const release = releases.find((r) => !r.draft && !r.prerelease && /^v?\d/.test(r.tag_name ?? ""));
821
775
  if (!release?.tag_name) return null;
822
776
  return { tag: release.tag_name, date: toDate(release.published_at) };
823
777
  } catch {
@@ -848,8 +802,7 @@ async function isEmptyDirectory(path2) {
848
802
 
849
803
  // src/create-cella-cli.ts
850
804
  async function main() {
851
- const templateVersion = await extractPackageJsonVersionFromUri(TEMPLATE_URL);
852
- showWelcome(templateVersion);
805
+ showWelcome();
853
806
  const promptTheme = { prefix: "", style: { answer: (text) => text } };
854
807
  const promptContext = { clearPromptOnDone: true };
855
808
  const templateRef = cli.options.template ? void 0 : await promptTemplateRef(promptTheme, promptContext);
@@ -925,8 +878,12 @@ async function promptTemplateRef(theme, context) {
925
878
  console.info(` ${pc6.gray(`https://github.com/${repo}/commit/${commit.sha}`)}`);
926
879
  }
927
880
  console.info();
928
- if (!release) return commit?.sha;
881
+ if (!release) return "main";
929
882
  if (!commit) return release.tag;
883
+ if (!process.stdin.isTTY) {
884
+ confirmChoice("Latest release", release.tag);
885
+ return release.tag;
886
+ }
930
887
  const choice = await select(
931
888
  {
932
889
  message: "Start from",
@@ -943,7 +900,7 @@ async function promptTemplateRef(theme, context) {
943
900
  return release.tag;
944
901
  }
945
902
  confirmChoice("Latest commit", commit.shortSha);
946
- return commit.sha;
903
+ return "main";
947
904
  }
948
905
  function formatOffset(o, suffix = "") {
949
906
  return `${o} \u2192 :${3e3 + o} / :${4e3 + o} / :${5432 + o}${suffix}`;
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/download-template.ts","../src/utils/list-template-files.ts","../src/utils/progress.ts","../src/utils/scan-optional-modules.ts","../src/utils/detect-used-ports.ts","../src/utils/extract-package-json-version-from-uri.ts","../src/utils/fetch-template-metadata.ts","../src/utils/is-empty-directory.ts"],"sourcesContent":["#!/usr/bin/env tsx\n\nimport { existsSync } from 'node:fs';\nimport { basename, resolve } from 'node:path';\nimport { input, select } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { TEMPLATE_URL } from '#/constants';\nimport { create } from '#/create';\nimport { type CreateOptions, cli, confirmChoice, showWelcome } from '#/modules/cli';\nimport { detectUsedPorts, findNextOffset } from '#/utils/detect-used-ports';\nimport { extractPackageJsonVersionFromUri } from '#/utils/extract-package-json-version-from-uri';\nimport { fetchLatestCommit, fetchLatestRelease } from '#/utils/fetch-template-metadata';\nimport { isEmptyDirectory } from '#/utils/is-empty-directory';\nimport { validateProjectName } from '#/utils/validate-project-name';\n\nasync function main(): Promise<void> {\n // Get the latest version of the template\n const templateVersion = await extractPackageJsonVersionFromUri(TEMPLATE_URL);\n\n // Display CLI welcome banner\n showWelcome(templateVersion);\n\n // Shared theme to clear prompts after answering\n const promptTheme = { prefix: '', style: { answer: (text: string) => text } };\n const promptContext = { clearPromptOnDone: true };\n\n // Choose which template snapshot to scaffold from: a stable release or the\n // latest commit. Skipped when a custom --template is provided (ref doesn't apply).\n const templateRef = cli.options.template ? undefined : await promptTemplateRef(promptTheme, promptContext);\n\n // Prompt for project name if not provided\n if (!cli.directory) {\n cli.directory = await input(\n {\n message: 'Enter your project name',\n default: 'my-cella-app',\n theme: promptTheme,\n validate: (name) => {\n const validation = validateProjectName(basename(resolve(name)));\n return validation.valid ? true : `Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`;\n },\n },\n promptContext,\n );\n }\n\n const targetFolder = resolve(cli.directory);\n const projectName = basename(targetFolder)?.toLowerCase() || 'project';\n\n // Check if the target folder exists and is not empty\n if (existsSync(targetFolder) && !(await isEmptyDirectory(targetFolder))) {\n const dirName = cli.directory === '.' ? 'Current directory' : `Target directory \"${targetFolder}\"`;\n const message = `${dirName} is not empty. Please choose how you would like to proceed:`;\n\n const action = await select(\n {\n message,\n theme: promptTheme,\n choices: [\n { name: 'Cancel and exit', value: 'cancel' },\n { name: 'Ignore existing files and continue', value: 'ignore' },\n ],\n },\n promptContext,\n );\n\n if (action === 'cancel') {\n process.exit(1);\n }\n }\n\n // Scan sibling directories and prompt for port offset\n const portOffset = cli.options.portOffset ?? (await promptPortOffset(targetFolder, promptTheme, promptContext));\n confirmChoice('Port offset', formatOffset(portOffset));\n\n // Prompt for admin email. Default uses the reserved `example.com` domain (RFC 2606) so the\n // seed address can never resolve or route real mail — it's a placeholder, not a real inbox.\n const adminEmail =\n cli.options.adminEmail ??\n (await input(\n {\n message: 'Admin email for initial seed user',\n default: 'admin@example.com',\n theme: promptTheme,\n },\n promptContext,\n ));\n confirmChoice('Admin email', adminEmail);\n\n // Proceed with the project creation\n const createOptions: CreateOptions = {\n projectName,\n targetFolder,\n packageManager: cli.packageManager,\n templateUrl: cli.options.template,\n templateRef,\n portOffset,\n adminEmail,\n };\n\n await create(createOptions);\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n\n/**\n * Show a short intro with the two starting points (latest release and latest\n * commit, each with its date) and let the user choose which template snapshot to\n * scaffold from. Returns the template ref (release tag or commit SHA), or undefined\n * to use the default branch (when metadata is unavailable).\n */\nasync function promptTemplateRef(theme: object, context: object): Promise<string | undefined> {\n const [release, commit] = await Promise.all([fetchLatestRelease(TEMPLATE_URL), fetchLatestCommit(TEMPLATE_URL)]);\n\n // No metadata (offline / rate-limited) — fall back to the default branch silently.\n if (!release && !commit) return undefined;\n\n // One-sentence intro followed by the two data points.\n console.info(pc.dim('You are about to install the Cella template.'));\n console.info();\n\n if (release) console.info(` Latest release: ${release.tag} ${pc.dim(`· ${release.date}`)}`);\n if (commit) {\n console.info(` Latest commit: ${commit.shortSha} ${pc.dim(`· ${commit.message} · ${commit.date}`)}`);\n // Clickable link to the exact commit on GitHub, in grey below the summary line.\n const repo = TEMPLATE_URL.replace('github:', '');\n console.info(` ${pc.gray(`https://github.com/${repo}/commit/${commit.sha}`)}`);\n }\n console.info();\n\n // Only one available — use it without prompting.\n if (!release) return commit?.sha;\n if (!commit) return release.tag;\n\n const choice = await select(\n {\n message: 'Start from',\n theme,\n choices: [\n { name: 'Latest release', value: 'release' },\n { name: 'Latest commit', value: 'commit' },\n ],\n },\n context,\n );\n\n // The prompt clears itself on done, so keep the choice visible with a checkmark.\n if (choice === 'release') {\n confirmChoice('Latest release', release.tag);\n return release.tag;\n }\n confirmChoice('Latest commit', commit.shortSha);\n return commit.sha;\n}\n\n/** Format an offset as a port overview string, e.g. \"10 → :3010 / :4010 / :5442\" */\nfunction formatOffset(o: number, suffix = ''): string {\n return `${o} → :${3000 + o} / :${4000 + o} / :${5432 + o}${suffix}`;\n}\n\n/** Scan sibling forks and prompt the user to pick a port offset */\nasync function promptPortOffset(targetFolder: string, theme: object, context: object): Promise<number> {\n const usedPorts = await detectUsedPorts(targetFolder);\n const suggested = findNextOffset(usedPorts);\n\n // Fold a compact hint into the prompt message itself so it's wiped by clearPromptOnDone\n // once an offset is chosen — no leftover \"Detected forks\" trail. Override the default\n // message style (which bolds everything) so only the \"Port offset\" title is bold.\n let message = `${pc.bold('Port offset')} ${pc.dim('· to avoid conflicts with other forks/clones')}`;\n if (usedPorts.length > 0) {\n const count = usedPorts.length;\n message += pc.dim(`\\nDetected ${count} sibling fork${count === 1 ? '' : 's'} · suggested offset ${suggested}`);\n }\n const portTheme = {\n ...(theme as Record<string, unknown>),\n style: { ...((theme as { style?: object }).style ?? {}), message: (text: string) => text },\n };\n\n // Presets exclude 0 (always offered as the explicit default below) and the suggested\n // value (offered first) to avoid duplicate choices.\n const presets = [10, 20, 30].filter((o) => o !== suggested);\n const choice = await select(\n {\n message,\n theme: portTheme,\n choices: [\n ...(suggested > 0 ? [{ name: formatOffset(suggested, ' (suggested)'), value: suggested }] : []),\n { name: formatOffset(0, ' (default)'), value: 0 },\n ...presets.map((o) => ({ name: formatOffset(o), value: o })),\n { name: 'Custom offset', value: -1 },\n ],\n },\n context,\n );\n\n if (choice !== -1) return choice;\n\n const custom = await input(\n {\n message: 'Enter custom offset (0-490, multiples of 10)',\n default: String(suggested),\n theme,\n validate: (val) => {\n const n = Number(val);\n if (Number.isNaN(n) || n < 0 || n > 490) return 'Must be between 0 and 490';\n if (n % 10 !== 0) return 'Must be a multiple of 10';\n return true;\n },\n },\n context,\n );\n return Number(custom);\n}\n","import pc from 'picocolors';\nimport packageJson from '../package.json' with { type: 'json' };\n\n/** Name of this CLI tool */\nexport const NAME = 'create cella';\n\n/** Thin line divider for console output (60 chars wide) */\nexport const DIVIDER = '─'.repeat(60);\n\n/** URL of the template repository */\nexport const TEMPLATE_URL = 'github:cellajs/cella';\n\n/** URL to the repository */\nexport const CELLA_REMOTE_URL = 'git@github.com:cellajs/cella.git';\n\n/** Export details from package.json */\nexport const DESCRIPTION: string = packageJson.description;\nexport const VERSION: string = packageJson.version;\nexport const WEBSITE: string = packageJson.homepage;\n\nexport function getHeaderLine(templateVersion?: string): string {\n const leftText = `⧈ ${NAME} · ${VERSION} · cella ${templateVersion}`;\n const left = `${pc.cyan(`⧈ ${NAME}`)} ${pc.dim(`· ${VERSION} · cella ${templateVersion}`)}`;\n const right = pc.cyan(WEBSITE);\n const padding = Math.max(1, 60 - leftText.length - WEBSITE.length);\n return `${left}${' '.repeat(padding)}${right}`;\n}\n\n// Files or folders to be removed from the template after downloading\nexport const TO_REMOVE: string[] = ['./cli/create', './info/QUICKSTART.md'];\n\n// Specific folder contents to be cleaned out from the template\nexport const TO_CLEAN: string[] = ['./backend/drizzle'];\n\n// Files to copy/paste after downloading\nexport const TO_COPY: Record<string, string> = {\n './frontend/.env.example': './frontend/.env',\n './info/QUICKSTART.md': 'README.md',\n};\n\n/**\n * Placeholder config template that replaces `shared/config/config.default.ts` in new forks.\n * Ships inside the create-cella package (see package.json `files`) and is resolved relative\n * to the package — NOT the downloaded fork. Contains `__project_name__` and `__project_slug__`\n * tokens interpolated at create time.\n */\nexport const PLACEHOLDER_CONFIG = 'configs/default-config.ts.template';\n\n/**\n * Read a `.env.example` file and apply key=value replacements.\n * Comments and unmatched keys are preserved as-is.\n * Returns null if the file doesn't exist (caller should generate from scratch).\n */\nexport async function generateEnvFromExample(\n examplePath: string,\n replacements: Record<string, string>,\n): Promise<string | null> {\n const { readFile } = await import('node:fs/promises');\n let content: string;\n try {\n content = await readFile(examplePath, 'utf8');\n } catch {\n return null;\n }\n\n return content.replace(/^([A-Z_][A-Z0-9_]*)=(.*)$/gm, (match, key, _value) => {\n if (key in replacements) return `${key}=${replacements[key]}`;\n return match;\n });\n}\n\n/**\n * Replacement map for `backend/.env`.\n * The backend `.env` is the single source of truth for the project slug, database\n * ports (consumed by `backend/compose.yaml`), connection URLs, admin email and API port.\n */\nexport function getBackendEnvReplacements(\n slug: string,\n adminEmail: string,\n portOffset: number,\n): Record<string, string> {\n const db = 5432 + portOffset;\n return {\n PROJECT_SLUG: slug,\n DB_PORT: String(db),\n DB_TEST_PORT: String(5434 + portOffset),\n DATABASE_URL: `postgres://runtime_role:dev_password@0.0.0.0:${db}/postgres`,\n DATABASE_ADMIN_URL: `postgres://postgres:postgres@0.0.0.0:${db}/postgres`,\n DATABASE_CDC_URL: `postgres://admin_role:dev_password@0.0.0.0:${db}/postgres`,\n ADMIN_EMAIL: adminEmail,\n PORT: String(4000 + portOffset),\n };\n}\n\n/**\n * Generate env config files with project-specific values.\n * All configs go through the same data-driven loop.\n * Values prefixed with '=' are emitted as raw TS expressions (not quoted).\n */\nexport function generateEnvConfigs(slug: string, name: string, portOffset: number): Record<string, string> {\n const fe = 3000 + portOffset;\n const be = 4000 + portOffset;\n\n const header =\n \"import type { DeepPartial } from '../src/config-builder/types';\\nimport type _default from './config.default';\\n\";\n\n // Per-environment specs: optional imports + object props (= prefix → raw TS expression)\n const envs: Record<string, { imports?: string; props: Record<string, string | boolean> }> = {\n development: {\n props: {\n slug: `${slug}-development`,\n domain: '',\n frontendUrl: `http://localhost:${fe}`,\n backendUrl: `http://localhost:${be}`,\n backendAuthUrl: `http://localhost:${be}/auth`,\n },\n },\n staging: {\n props: {\n slug: `${slug}-staging`,\n domain: `${slug}.example.com`,\n frontendUrl: `https://staging.${slug}.example.com`,\n backendUrl: `https://api-staging.${slug}.example.com`,\n backendAuthUrl: `https://api-staging.${slug}.example.com/auth`,\n },\n },\n tunnel: {\n props: {\n frontendUrl: `https://localhost:${fe}`,\n backendUrl: `https://${slug}.ngrok.dev`,\n backendAuthUrl: `https://${slug}.ngrok.dev/auth`,\n },\n },\n test: {\n imports: \"import development from './config.development';\\n\",\n props: {\n domain: '',\n frontendUrl: '=development.frontendUrl',\n backendUrl: '=development.backendUrl',\n backendAuthUrl: '=development.backendAuthUrl',\n },\n },\n production: { props: { maintenance: false } },\n };\n\n // Serialize value: '=' prefix → raw TS expression, boolean → literal, string → quoted\n const lit = (v: string | boolean) => {\n if (typeof v === 'boolean') return String(v);\n if (v.startsWith('=')) return v.slice(1);\n return `'${v}'`;\n };\n\n const result: Record<string, string> = {};\n\n for (const [mode, { imports = '', props }] of Object.entries(envs)) {\n const nameEntry = mode !== 'production' ? ` name: '${name} ${mode.toUpperCase()}',\\n` : '';\n const body = Object.entries(props)\n .map(([k, v]) => ` ${k}: ${lit(v)},`)\n .join('\\n');\n result[`./shared/config/config.${mode}.ts`] =\n `${imports}${header}\\nexport default {\\n mode: '${mode}',\\n${nameEntry}${body}\\n} satisfies DeepPartial<typeof _default>;\\n`;\n }\n\n return result;\n}\n","{\n \"name\": \"@cellajs/create-cella\",\n \"version\": \"0.3.0\",\n \"private\": false,\n \"license\": \"MIT\",\n \"description\": \"Cella is a TypeScript template to create web apps with sync and offline capabilities.\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/cellajs/create-cella\"\n },\n \"homepage\": \"https://cellajs.com\",\n \"author\": \"CellaJS <info@cellajs.com>\",\n \"engines\": {\n \"node\": \"24.x\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"configs\",\n \"dist\",\n \"index.js\",\n \"README.md\"\n ],\n \"imports\": {\n \"#/*\": \"./src/*\"\n },\n \"bin\": {\n \"create-cella\": \"index.js\"\n },\n \"scripts\": {\n \"start\": \"tsx ./src/create-cella-cli.ts\",\n \"try\": \"tsx ./src/create-cella-cli.ts ../cella-try-app\",\n \"clean\": \"rimraf ./dist\",\n \"build\": \"tsup\",\n \"test-build\": \"pnpm run build && node index.js\",\n \"prepublishOnly\": \"pnpm run test:release\",\n \"check\": \"pnpm ts && pnpm biome check --write .\",\n \"ts\": \"tsgo --pretty\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"test\": \"vitest run\",\n \"test:release\": \"pnpm run build && vitest run --config vitest.release.config.ts\",\n \"test:watch\": \"vitest\",\n \"prepare\": \"lefthook install\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^8.5.2\",\n \"commander\": \"^15.0.0\",\n \"ignore\": \"^7.0.5\",\n \"isomorphic-git\": \"^1.38.5\",\n \"nanotar\": \"^0.3.0\",\n \"ora\": \"^9.4.1\",\n \"picocolors\": \"^1.1.1\",\n \"validate-npm-package-name\": \"^8.0.0\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.5.1\",\n \"@commitlint/cli\": \"^21.1.0\",\n \"@commitlint/config-conventional\": \"^21.1.0\",\n \"@evilmartians/lefthook\": \"^2.1.9\",\n \"@types/node\": \"24.10.13\",\n \"@types/validate-npm-package-name\": \"^4.0.2\",\n \"@typescript/native-preview\": \"7.0.0-dev.20260628.1\",\n \"rimraf\": \"^6.1.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.22.4\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.9\"\n },\n \"packageManager\": \"pnpm@11.8.0\",\n \"commitlint\": {\n \"extends\": [\n \"@commitlint/config-conventional\"\n ],\n \"rules\": {\n \"type-enum\": [\n 2,\n \"always\",\n [\n \"feat\",\n \"fix\",\n \"perf\",\n \"refactor\",\n \"revert\",\n \"docs\",\n \"chore\",\n \"build\",\n \"ci\",\n \"style\",\n \"test\"\n ]\n ]\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { cp, mkdir, rm } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { checkbox } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { addRemote } from '#/add-remote';\nimport { TEMPLATE_URL } from '#/constants';\nimport { type CreateOptions, confirmChoice, showSuccess } from '#/modules/cli';\nimport { cleanTemplate } from '#/utils/clean-template';\nimport { downloadGithubTemplate } from '#/utils/download-template';\nimport { gitAddAll, gitCommit, gitInit } from '#/utils/git';\nimport { listTemplateFiles } from '#/utils/list-template-files';\nimport { createProgress, pauseSpinner, resumeSpinner } from '#/utils/progress';\nimport { optionalModuleFolders, scanOptionalModules } from '#/utils/scan-optional-modules';\n\n/** Check if a path is a local directory */\nfunction isLocalPath(path: string): boolean {\n return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');\n}\n\nfunction shouldSkipStep(name: 'git' | 'remote'): boolean {\n return process.env[`CREATE_CELLA_SKIP_${name.toUpperCase()}`] === 'true';\n}\n\nexport async function create({\n projectName,\n targetFolder,\n packageManager,\n templateUrl,\n templateRef,\n portOffset,\n adminEmail,\n silent = false,\n}: CreateOptions): Promise<void> {\n // Save the original working directory\n const originalCwd = process.cwd();\n\n // Use custom template or default\n const template = templateUrl || TEMPLATE_URL;\n const isLocalTemplate = templateUrl && isLocalPath(templateUrl);\n\n const progress = createProgress('creating project', silent);\n\n try {\n // Create the target folder\n progress.step('creating project folder');\n await mkdir(targetFolder, { recursive: true });\n process.chdir(targetFolder);\n\n // Download or copy the template\n if (isLocalTemplate) {\n progress.step('copying local template');\n const sourcePath = resolve(originalCwd, templateUrl);\n\n // Check if target is inside source (would cause EINVAL). Use a path-boundary check\n // (not a string prefix) so siblings like `cella-try-app` next to `cella` aren't\n // misdetected as being inside the template.\n const rel = relative(sourcePath, targetFolder);\n const targetInsideTemplate = rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n if (targetInsideTemplate) {\n throw new Error(\n 'Cannot create project inside the template directory.\\n' +\n ` Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`,\n );\n }\n\n // Copy only what git would track (respects .gitignore: no build output, no local .env secrets).\n const tracked = await listTemplateFiles(sourcePath);\n for (const rel of tracked) {\n const src = join(sourcePath, rel);\n if (!existsSync(src)) continue; // listed but deleted in the working tree\n const dest = join(targetFolder, rel);\n await mkdir(dirname(dest), { recursive: true });\n await cp(src, dest);\n }\n } else {\n // Pin to the chosen ref (release tag or commit SHA), else the default branch.\n const ref = templateRef ?? 'main';\n progress.step(`downloading cella template${templateRef ? ` (${templateRef})` : ''}`);\n await downloadGithubTemplate(template, ref, targetFolder);\n }\n\n // Ask which optional modules to keep, then drop deselected paths\n if (!silent) await promptOptionalModules(targetFolder);\n\n // Clean the template, generate configs\n progress.step('cleaning template');\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });\n\n // Initialize git repository\n const gitFolderPath = join(targetFolder, '.git');\n if (!shouldSkipStep('git') && !existsSync(gitFolderPath)) {\n progress.step('initializing git');\n await gitInit(targetFolder);\n await gitAddAll(targetFolder);\n await gitCommit(targetFolder, 'Initial commit');\n }\n\n // Add upstream remote\n if (!shouldSkipStep('remote')) {\n progress.step('adding upstream remote');\n await addRemote({ targetFolder, silent: true });\n }\n\n // Done\n progress.done(pc.bold(`created ${projectName}`));\n } catch (error) {\n progress.fail(error instanceof Error ? error.message : String(error));\n // Rethrow so callers (CLI entry, tests) see the real failure. The CLI entry\n // turns this into a non-zero exit; swallowing it here with process.exit hid\n // the underlying error from CI and vitest.\n throw error;\n }\n\n // Check if the working directory needs to be changed\n const needsCd = originalCwd !== targetFolder;\n const relativePath = relative(originalCwd, targetFolder);\n\n // Display final success message\n showSuccess(projectName, targetFolder, relativePath, needsCd, packageManager);\n}\n\n/**\n * Scan the downloaded template for optional modules and let the user deselect them.\n * Deselected modules have their whole folder removed before cleaning. Default keeps\n * everything (parity with previous behavior).\n */\nasync function promptOptionalModules(targetFolder: string): Promise<void> {\n const modules = await scanOptionalModules(targetFolder);\n if (modules.length === 0) return;\n\n // Non-interactive (CI, piped stdin, no TTY): can't render a checkbox — keep every module\n // rather than crashing with \"User force closed the prompt\". Matches the default-keep behavior.\n if (!process.stdin.isTTY) return;\n\n pauseSpinner();\n // Blank line to separate the preceding progress step from this prompt.\n console.info();\n const keep = await checkbox(\n {\n message: 'Optional modules to include',\n // Leading space on the radio icons adds a gap after the cursor (inquirer renders\n // `${cursor}${checkbox} ${name}` with no space between cursor and checkbox).\n theme: { icon: { checked: ` ${pc.green('◉')}`, unchecked: ' ◯' } },\n choices: modules.map((m) => ({\n name: `${m.name} ${pc.dim(`· ${m.description}`)}`,\n value: m.name,\n checked: true,\n })),\n },\n // Clear the answered prompt so we can print a compact, non-bold summary that lists\n // only the kept module names (not their descriptions).\n { clearPromptOnDone: true },\n );\n\n // Keep the choice visible after the prompt clears: plain label plus the selected\n // module names only (not their descriptions), or \"none\" when everything was deselected.\n confirmChoice('Optional modules', keep.length ? keep.join(', ') : pc.dim('none'));\n resumeSpinner();\n\n // Remove every folder owned by a deselected module. `optionalModuleFolders` lists more\n // route variants than any single module uses; `force: true` ignores the ones absent here.\n const folders = modules.filter((m) => !keep.includes(m.name)).flatMap(optionalModuleFolders);\n await Promise.all(folders.map((f) => rm(join(targetFolder, f), { recursive: true, force: true })));\n}\n","import fs from 'node:fs';\nimport git from 'isomorphic-git';\n\n/**\n * Git helpers backed by isomorphic-git (pure JS) instead of shelling out to the\n * `git` binary. This keeps `create-cella` free of `child_process` (no shell access)\n * and removes any dependency on the user's global git config — the initial commit\n * uses an explicit author, so scaffolding never fails with \"Author identity unknown\".\n */\n\n/** Author used for the scaffold's initial commit (users re-author their own commits later). */\nconst INITIAL_AUTHOR = { name: 'cella', email: 'info@cellajs.com' } as const;\n\n/**\n * Initializes a new Git repository in the specified directory.\n */\nexport async function gitInit(repoPath: string): Promise<void> {\n await git.init({ fs, dir: repoPath, defaultBranch: 'main' });\n}\n\n/**\n * Stages all files in the repository (respects `.gitignore`).\n */\nexport async function gitAddAll(repoPath: string): Promise<void> {\n await git.add({ fs, dir: repoPath, filepath: '.' });\n}\n\n/**\n * Creates a commit with the specified message and returns the commit SHA.\n */\nexport async function gitCommit(repoPath: string, message: string): Promise<string> {\n return git.commit({ fs, dir: repoPath, message, author: INITIAL_AUTHOR });\n}\n\n/**\n * Gets the URL of a remote. Throws when the remote does not exist (mirrors\n * `git remote get-url` failing) so callers can detect a missing remote.\n */\nexport async function gitRemoteGetUrl(repoPath: string, remoteName: string): Promise<string> {\n const remotes = await git.listRemotes({ fs, dir: repoPath });\n const match = remotes.find((r) => r.remote === remoteName);\n if (!match) throw new Error(`No such remote '${remoteName}'`);\n return match.url;\n}\n\n/**\n * Adds a new remote.\n */\nexport async function gitRemoteAdd(repoPath: string, remoteName: string, remoteUrl: string): Promise<void> {\n await git.addRemote({ fs, dir: repoPath, remote: remoteName, url: remoteUrl });\n}\n\n/**\n * Removes a remote.\n */\nexport async function gitRemoteRemove(repoPath: string, remoteName: string): Promise<void> {\n await git.deleteRemote({ fs, dir: repoPath, remote: remoteName });\n}\n","import { CELLA_REMOTE_URL } from '#/constants';\nimport type { AddRemoteOptions } from '#/modules/cli';\nimport { gitRemoteAdd, gitRemoteGetUrl, gitRemoteRemove } from '#/utils/git';\n\n/**\n * Adds or updates the upstream remote for the Cella template.\n * @param options - Configuration options\n * @param options.silent - If true, don't throw on failure (used when called from progress tracker)\n */\nexport async function addRemote({\n targetFolder,\n remoteUrl = CELLA_REMOTE_URL,\n remoteName = 'upstream',\n silent = false,\n}: AddRemoteOptions): Promise<void> {\n try {\n // Check if the remote exists\n let remote: string | null = null;\n\n try {\n remote = await gitRemoteGetUrl(targetFolder, remoteName);\n } catch {\n // If the remote doesn't exist, it throws a fatal error\n remote = null;\n }\n\n // Add or update the remote if it doesn't exist or differs from `remoteUrl`\n if (!remote) {\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n } else if (remote !== remoteUrl) {\n // Remove existing remote and set the new URL\n await gitRemoteRemove(targetFolder, remoteName);\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n }\n } catch (error) {\n if (!silent) {\n throw error;\n }\n }\n}\n","import { basename, resolve } from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { NAME, VERSION } from '#/constants';\nimport { validateProjectName } from '#/utils/validate-project-name';\nimport type { CLIConfig, CLIOptions } from './types';\n\nfunction parsePortOffset(value: string): number {\n const parsed = Number(value);\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 490) {\n throw new InvalidArgumentError('Port offset must be an integer between 0 and 490');\n }\n\n if (parsed % 10 !== 0) {\n throw new InvalidArgumentError('Port offset must be a multiple of 10');\n }\n\n return parsed;\n}\n\n// Initialize CLI variables\nlet directory: string | null = null;\nconst packageManager = 'pnpm';\n\n/**\n * Defines the root CLI command using Commander.\n * This command accepts CLI options and validates user input.\n */\nexport const command = new Command(NAME)\n .version(VERSION, '-v, --version', `output the current version of ${NAME}`)\n .argument('[directory]', 'the directory name for the new project')\n .usage('[directory] [options]')\n .helpOption('-h, --help', 'display this help message')\n .option('--template <path>', 'use a custom template (local path or github:user/repo)')\n .option('--port-offset <number>', 'set the port offset (0-490 in steps of 10)', parsePortOffset)\n .option('--admin-email <email>', 'set the admin email for the initial seed user')\n .action((name: string) => {\n const trimmedName = typeof name === 'string' ? name.trim() : name;\n\n if (trimmedName) {\n const validation = validateProjectName(basename(resolve(trimmedName)));\n\n if (!validation.valid) {\n throw new InvalidArgumentError(`Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`);\n }\n\n directory = trimmedName;\n }\n })\n .parse();\n\n// Gather the CLI options and arguments\nconst options: CLIOptions = command.opts<CLIOptions>();\n\n/**\n * Runs the CLI and returns the parsed configuration.\n * This function parses command line arguments and returns the CLI config.\n */\nexport function runCli(): CLIConfig {\n return {\n options,\n args: command.args,\n directory,\n packageManager,\n };\n}\n\n// Export CLI configuration for direct import\nexport const cli: CLIConfig = runCli();\n","import validate from 'validate-npm-package-name';\n\ninterface ValidationResult {\n valid: boolean;\n problems?: string[];\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n const nameValidation = validate(name);\n\n if (nameValidation.validForNewPackages) {\n return { valid: true };\n }\n\n return {\n valid: false,\n problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])],\n };\n}\n","import pc from 'picocolors';\nimport { DESCRIPTION, DIVIDER, getHeaderLine } from '#/constants';\n\n/** ASCII art logo for the CLI welcome screen. */\nfunction showAscii(): void {\n console.info(pc.cyan(' _ _ '));\n console.info(pc.cyan('▒▓█████▓▒ ___ ___| | | __ _ '));\n console.info(pc.cyan('▒▓█ █▓▒ / __/ _ \\\\ | |/ _` | '));\n console.info(pc.cyan('▒▓█ █▓▒ | (_| __/ | | (_| | '));\n console.info(pc.cyan('▒▓█████▓▒ \\\\___\\\\___|_|_|\\\\__,_| '));\n}\n\n/**\n * Displays the compact CLI welcome header.\n * @param templateVersion - The version of the cella template being used\n */\nexport function showWelcome(templateVersion: string): void {\n console.info();\n showAscii();\n console.info();\n console.info(pc.dim(DESCRIPTION));\n console.info();\n console.info(getHeaderLine(templateVersion));\n console.info(DIVIDER);\n}\n\n/**\n * Print a cleared prompt's outcome as a persistent confirmation line:\n * `✓ <label> · <value>`, followed by a blank separator line. Used after prompts\n * that clear themselves on done so the chosen value stays visible.\n */\nexport function confirmChoice(label: string, value: string): void {\n console.info(`${pc.green('✓')} ${label} · ${value}`);\n console.info();\n}\n\n/**\n * Displays the final success message after project creation.\n */\nexport function showSuccess(\n projectName: string,\n _targetFolder: string,\n relativePath: string,\n needsCd: boolean,\n packageManager: string,\n): void {\n console.info();\n\n console.info(pc.bold('Next steps'));\n console.info(DIVIDER);\n console.info();\n\n if (needsCd) {\n console.info(`${pc.dim('$')} cd ${pc.cyan(relativePath)}`);\n }\n console.info(`${pc.dim('$')} ${pc.cyan(`${packageManager} install`)}`);\n console.info(`${pc.dim('$')} ${pc.cyan(`${packageManager} generate`)}`);\n console.info(\n `${pc.dim('$')} ${pc.cyan(`${packageManager} docker`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} seed`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} dev`)}`,\n );\n console.info();\n\n // Point to the generated README for first-run details and sign-in credentials. Use the\n // path relative to the new project so the terminal linkifies the right file (not this CLI's).\n const readmePath = relativePath ? `${relativePath}/README.md` : 'README.md';\n console.info(`${pc.gray('To get started building, please read:')} ${pc.cyan(readmePath)}`);\n console.info();\n console.info(`🥳 enjoy building ${pc.green(projectName)} with cella!`);\n console.info();\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\nimport {\n generateEnvConfigs,\n generateEnvFromExample,\n getBackendEnvReplacements,\n PLACEHOLDER_CONFIG,\n TO_CLEAN,\n TO_COPY,\n TO_REMOVE,\n} from '#/constants';\n\nconst warningMark = pc.yellow('⚠');\n\n/**\n * Resolve the placeholder config template that ships inside the create-cella package.\n *\n * The template is the CLI's own asset (listed in package.json `files`), NOT part of the\n * downloaded fork. It must therefore be read relative to this module, which lives at:\n * - dev (tsx): `<pkg>/src/utils/clean-template.ts` → template at `../../<PLACEHOLDER_CONFIG>`\n * - bundled: `<pkg>/dist/index.js` → template at `../<PLACEHOLDER_CONFIG>`\n */\nasync function resolvePlaceholderConfigPath(): Promise<string> {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [path.resolve(here, '../..', PLACEHOLDER_CONFIG), path.resolve(here, '..', PLACEHOLDER_CONFIG)];\n for (const candidate of candidates) {\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n // try next candidate\n }\n }\n // Fall back to the first candidate so the caller surfaces a clear ENOENT.\n return candidates[0];\n}\n\n/**\n * Cleans the specified template by removing designated folders and files.\n * @param params - Parameters containing the target folder, project name, and optional extra edits.\n */\nexport async function cleanTemplate({\n targetFolder,\n projectName,\n displayName,\n portOffset = 0,\n adminEmail = `admin@${projectName}.example.com`,\n}: {\n targetFolder: string;\n projectName: string;\n displayName: string;\n portOffset?: number;\n adminEmail?: string;\n}): Promise<void> {\n // Change the current working directory to targetFolder if not already set\n if (process.cwd() !== targetFolder) {\n process.chdir(targetFolder);\n }\n\n return new Promise<void>((resolve, reject) => {\n (async () => {\n try {\n // Copy specified files\n for (const [src, dest] of Object.entries(TO_COPY)) {\n const srcAbsolutePath = path.resolve(targetFolder, src);\n const destAbsolutePath = path.resolve(targetFolder, dest);\n await copyFile(srcAbsolutePath, destAbsolutePath);\n }\n\n // Replace config.default.ts with interpolated placeholder config\n await applyPlaceholderConfig(targetFolder, projectName);\n\n // Generate backend .env from backend/.env.example.\n // The backend .env is the single source of truth for the project slug and DB ports\n // (consumed by backend/compose.yaml). There is no root .env.\n const backendReplacements = getBackendEnvReplacements(projectName, adminEmail, portOffset);\n const backendEnv = await generateEnvFromExample(\n path.resolve(targetFolder, 'backend/.env.example'),\n backendReplacements,\n );\n if (backendEnv) {\n await fs.writeFile(path.resolve(targetFolder, 'backend/.env'), backendEnv, 'utf8');\n }\n\n // Generate minimal env config files with project values and ports baked in\n const envConfigs = generateEnvConfigs(projectName, displayName, portOffset);\n await Promise.all(\n Object.entries(envConfigs).map(([filePath, content]) =>\n fs.writeFile(path.resolve(targetFolder, filePath), content, 'utf8'),\n ),\n );\n\n // Clean specified folder contents\n await Promise.all(\n TO_CLEAN.map((folderPath) => {\n const absolutePath = path.resolve(targetFolder, folderPath);\n return removeFolderContents(absolutePath);\n }),\n );\n\n // Remove specified files and folders\n await Promise.all(\n TO_REMOVE.map((filePath) => {\n const absolutePath = path.resolve(targetFolder, filePath);\n return removeFileOrFolder(absolutePath);\n }),\n );\n\n resolve();\n } catch (err) {\n reject(`Error during the cleaning process: ${err}`);\n }\n })();\n });\n}\n\n/**\n * Removes all contents within a specified folder.\n * @param folderPath - The path of the folder to clean.\n */\nasync function removeFolderContents(folderPath: string): Promise<void> {\n // List all files in the folder\n const files = await fs.readdir(folderPath);\n\n await Promise.all(\n files.map(async (file) => {\n const filePath = path.join(folderPath, file);\n\n // Get the file or folder statistics\n const stat = await fs.lstat(filePath);\n\n // If it's a directory, remove it and all its contents\n if (stat.isDirectory()) {\n await fs.rm(filePath, { recursive: true, force: true });\n } else {\n // If it's a file, remove it\n await fs.rm(filePath);\n }\n }),\n );\n}\n\n/**\n * Removes a specified file or folder.\n * @param pathToRemove - The path to the file or folder to remove.\n */\nasync function removeFileOrFolder(pathToRemove: string): Promise<void> {\n await fs.rm(pathToRemove, { recursive: true, force: true });\n}\n\n/**\n * Helper function to copy files if the source exists.\n * @param src - The source file path.\n * @param dest - The destination file path.\n */\nasync function copyFile(src: string, dest: string): Promise<void> {\n try {\n // Check if the source file exists\n await fs.access(src);\n\n // Ensure the destination directory exists\n await fs.mkdir(path.dirname(dest), { recursive: true });\n\n // Copy the file\n await fs.copyFile(src, dest);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Source file \"${src}\" does not exist > Skip copy`);\n } else {\n throw err;\n }\n }\n}\n\n/**\n * Read the placeholder config template, interpolate project tokens, and\n * write it as `shared/config/config.default.ts` — replacing the original.\n */\nasync function applyPlaceholderConfig(targetFolder: string, projectName: string): Promise<void> {\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n\n const src = await resolvePlaceholderConfigPath();\n const dest = path.resolve(targetFolder, './shared/config/config.default.ts');\n\n try {\n let content = await fs.readFile(src, 'utf8');\n content = content.replaceAll('__project_name__', displayName);\n content = content.replaceAll('__project_slug__', projectName);\n await fs.writeFile(dest, content, 'utf8');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Placeholder config \"${src}\" not found > Skip`);\n } else {\n throw err;\n }\n }\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { parseTarGzip } from 'nanotar';\n\n/** Parse `github:owner/repo` or `owner/repo` into its owner and repo parts. */\nfunction parseGithubRepo(template: string): { owner: string; repo: string } {\n const [owner, repo] = template.replace(/^github:/, '').split('/');\n if (!owner || !repo) {\n throw new Error(`Invalid template '${template}'. Expected 'github:owner/repo'.`);\n }\n return { owner, repo };\n}\n\n/**\n * Downloads a GitHub repository tarball and extracts it into `targetFolder`.\n *\n * Uses the codeload `tar.gz` endpoint with a plain `fetch` and pure-JS gunzip +\n * untar (nanotar, via the Web `DecompressionStream`). No git, no `child_process`,\n * so `create-cella` keeps a clean supply-chain profile (no shell access).\n *\n * @param template - `github:owner/repo` (or `owner/repo`).\n * @param ref - Release tag, commit SHA, or branch to download.\n * @param targetFolder - Directory to extract the template into.\n */\nexport async function downloadGithubTemplate(template: string, ref: string, targetFolder: string): Promise<void> {\n const { owner, repo } = parseGithubRepo(template);\n const url = `https://codeload.github.com/${owner}/${repo}/tar.gz/${ref}`;\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to download template ${owner}/${repo}@${ref}: ${response.status} ${response.statusText}`);\n }\n\n const archive = new Uint8Array(await response.arrayBuffer());\n const entries = await parseTarGzip(archive);\n\n for (const entry of entries) {\n if (entry.type !== 'file' || !entry.data) continue;\n // GitHub nests everything under a top-level `repo-ref/` folder — strip it.\n const relativePath = entry.name.replace(/^[^/]+\\//, '');\n if (!relativePath) continue;\n\n const dest = join(targetFolder, relativePath);\n await mkdir(dirname(dest), { recursive: true });\n // Preserve the executable bit (nanotar reports mode as an octal string).\n const mode = entry.attrs?.mode ? Number.parseInt(entry.attrs.mode, 8) : undefined;\n await writeFile(dest, entry.data, mode ? { mode } : undefined);\n }\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport ignore from 'ignore';\n\n/**\n * Lists the files under `root` that git would track, without shelling out to git.\n *\n * Honors the root `.gitignore` (via the `ignore` package) and always skips `.git`\n * and `node_modules`. Used for the local-template dev flow (`--template ./local`)\n * so build output and local `.env` secrets aren't copied into the new project.\n *\n * Paths are returned relative to `root` using POSIX separators.\n */\nexport async function listTemplateFiles(root: string): Promise<string[]> {\n const ig = ignore().add(['.git', 'node_modules']);\n try {\n ig.add(await readFile(join(root, '.gitignore'), 'utf8'));\n } catch {\n // No .gitignore at the template root — only the defaults above apply.\n }\n\n const files: string[] = [];\n\n async function walk(relDir: string): Promise<void> {\n const entries = await readdir(relDir ? join(root, relDir) : root, { withFileTypes: true });\n for (const entry of entries) {\n const rel = relDir ? `${relDir}/${entry.name}` : entry.name;\n // `ignore` matches directory rules when the path ends with a slash.\n if (ig.ignores(entry.isDirectory() ? `${rel}/` : rel)) continue;\n if (entry.isDirectory()) await walk(rel);\n else if (entry.isFile()) files.push(rel);\n }\n }\n\n await walk('');\n return files;\n}\n","/**\n * Progress Tracker Utility\n *\n * Provides compact progress tracking for CLI operations.\n * Uses a single spinner that updates in place.\n */\n\nimport ora, { type Ora } from 'ora';\nimport pc from 'picocolors';\n\n/** Global reference to the currently active spinner */\nlet activeSpinner: Ora | null = null;\n\n/** Pause the active spinner (for interactive prompts) */\nexport function pauseSpinner(): void {\n if (activeSpinner) {\n activeSpinner.stop();\n }\n}\n\n/** Resume the active spinner after a prompt */\nexport function resumeSpinner(): void {\n if (activeSpinner) {\n activeSpinner.start();\n }\n}\n\nexport interface ProgressTracker {\n /** Update the current step (shown as spinner text) */\n step: (message: string) => void;\n /** Mark the tracker as complete with final message */\n done: (message: string) => void;\n /** Mark the tracker as failed with error message */\n fail: (message: string) => void;\n}\n\n/**\n * Creates a progress tracker for a multi-step operation.\n * Shows a single spinner that updates in place.\n *\n * @param title - The initial title for the spinner\n * @returns A ProgressTracker instance\n *\n * @example\n * const progress = createProgress('creating project');\n * progress.step('downloading template');\n * progress.step('installing dependencies');\n * progress.done('project created');\n */\nexport function createProgress(title: string, silent = false): ProgressTracker {\n const completedSteps: string[] = [];\n\n const spinner = ora({\n text: title,\n isSilent: silent,\n });\n\n activeSpinner = spinner;\n spinner.start();\n\n // Helper to log (respects silent mode)\n const log = (msg: string) => {\n if (!silent) console.info(msg);\n };\n\n return {\n step: (message: string) => {\n // Complete previous step with green check if there was one\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n }\n completedSteps.push(message);\n spinner.text = message;\n spinner.start();\n },\n\n done: (message: string) => {\n // Complete the last step with green check\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n } else {\n spinner.stop();\n }\n activeSpinner = null;\n if (message) {\n log('');\n log(`${pc.green('✓')} ${message}`);\n }\n },\n\n fail: (message: string) => {\n spinner.stop();\n activeSpinner = null;\n log(`${pc.red('✗')} ${pc.red(message)}`);\n },\n };\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\n\n/** An optional module discovered in a `*-module.ts` registration. */\nexport interface OptionalModule {\n name: string;\n description: string;\n /** Module folder, relative to the project root, removed when deselected. */\n folder: string;\n}\n\n/** Directories never worth scanning. */\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage']);\n\n/**\n * Scan the template for `*-module.ts` files that register `optional: true` and return\n * their name, description and folder. Pure static regex parsing — no imports/eval — so it\n * runs before dependencies are installed. The folder is the module file's directory.\n */\nexport async function scanOptionalModules(targetFolder: string): Promise<OptionalModule[]> {\n const files: string[] = [];\n await collectModuleFiles(targetFolder, files);\n\n const modules: OptionalModule[] = [];\n for (const file of files) {\n const src = await readFile(file, 'utf8');\n if (!/optional:\\s*true/.test(src)) continue;\n\n const name = src.match(/name:\\s*['\"]([^'\"]+)['\"]/)?.[1];\n const description = src.match(/description:\\s*['\"]([^'\"]+)['\"]/)?.[1] ?? '';\n if (!name) continue;\n\n modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });\n }\n return modules.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Every project-root-relative folder removed when an optional module is deselected: its own\n * module folder plus the route and static-asset folders it may own. Route folders are\n * speculative — a module owns either a pathless `_<name>` (URL-transparent) or a path-based\n * `<name>` route, under `routes/_public` and/or `routes/_app` — so callers remove only the\n * variants that actually exist. Single-sourced here so removal and tests stay in sync.\n */\nexport function optionalModuleFolders({ name, folder }: OptionalModule): string[] {\n return [\n folder,\n `frontend/src/routes/_public/_${name}`,\n `frontend/src/routes/_public/${name}`,\n `frontend/src/routes/_app/_${name}`,\n `frontend/src/routes/_app/${name}`,\n `frontend/public/static/${name}`,\n ];\n}\n\nasync function collectModuleFiles(dir: string, out: string[]): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await collectModuleFiles(join(dir, entry.name), out);\n } else if (entry.name.endsWith('-module.ts')) {\n out.push(join(dir, entry.name));\n }\n }\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\ninterface UsedPorts {\n project: string;\n frontend: number;\n backend: number;\n offset: number;\n}\n\n/**\n * Scan sibling directories for existing cella forks and extract their dev ports.\n * Looks for `shared/config/config.development.ts` to identify cella-based projects.\n */\nexport async function detectUsedPorts(targetFolder: string): Promise<UsedPorts[]> {\n const parentDir = dirname(targetFolder);\n const used: UsedPorts[] = [];\n\n let siblings: string[];\n try {\n siblings = await readdir(parentDir);\n } catch {\n return used;\n }\n\n for (const name of siblings) {\n const configPath = join(parentDir, name, 'shared/config/config.development.ts');\n try {\n const content = await readFile(configPath, 'utf8');\n const feMatch = content.match(/frontendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n const beMatch = content.match(/backendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n if (feMatch && beMatch) {\n const frontend = Number(feMatch[1]);\n const backend = Number(beMatch[1]);\n used.push({\n project: name,\n frontend,\n backend,\n offset: frontend - 3000,\n });\n }\n } catch {\n // Not a cella fork, skip\n }\n }\n\n return used;\n}\n\n/** Find the next available offset (in steps of 10) that doesn't collide with existing forks. */\nexport function findNextOffset(usedPorts: UsedPorts[]): number {\n const usedOffsets = new Set(usedPorts.map((p) => p.offset));\n for (let offset = 0; offset <= 490; offset += 10) {\n if (!usedOffsets.has(offset)) return offset;\n }\n return 0;\n}\n","/**\n * Retrieves the version from the package.json file of a GitHub repository.\n * If the package.json file is not found or an error occurs, it returns 'unknown'.\n *\n * @param repositoryUrl {string} - The GitHub repository URL in the format 'github:user/repo'.\n * @param branch {string} - The branch name (defaults to 'main').\n * @returns {Promise<string>} - The version from the package.json file.\n */\nexport async function extractPackageJsonVersionFromUri(repositoryUrl: string, branch = 'main'): Promise<string> {\n // Extract owner and repo from the URL\n const [owner, repo] = repositoryUrl.replace('github:', '').split('/');\n\n // Use the GitHub contents API to fetch the package.json file for the given branch\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${branch}`;\n\n try {\n const response = await fetch(apiUrl, {\n headers: { Accept: 'application/vnd.github.raw+json' },\n });\n if (!response.ok) return 'unknown';\n\n const packageJson = (await response.json()) as { version?: string };\n\n // Return the version from the package.json, or 'unknown' if not found\n return packageJson.version || 'unknown';\n } catch {\n // If there's an error (file not found, etc.), return 'unknown'\n return 'unknown';\n }\n}\n","/**\n * Fetch lightweight metadata about the cella template from the GitHub API so the\n * scaffolder can offer \"latest release\" vs \"latest commit\" as a starting point.\n *\n * Both helpers fail soft (return null) on network/API errors or rate limits — the\n * caller falls back to the default branch when metadata is unavailable.\n */\n\n/** Parse 'github:owner/repo' (or 'owner/repo') into [owner, repo]. */\nfunction parseRepo(repositoryUrl: string): [string, string] {\n const [owner, repo] = repositoryUrl.replace('github:', '').split('/');\n return [owner, repo];\n}\n\n/** Format an ISO timestamp as a locale-independent YYYY-MM-DD date. */\nfunction toDate(iso?: string): string {\n return iso ? iso.slice(0, 10) : 'unknown';\n}\n\nconst GITHUB_HEADERS = { Accept: 'application/vnd.github+json' } as const;\n\nexport interface ReleaseInfo {\n /** Release tag, e.g. 'v0.5.0' */\n tag: string;\n /** Publication date, YYYY-MM-DD */\n date: string;\n}\n\nexport interface CommitInfo {\n /** Full commit SHA (used for a reproducible checkout) */\n sha: string;\n /** Short 7-char SHA for display */\n shortSha: string;\n /** First line of the commit message */\n message: string;\n /** Commit date, YYYY-MM-DD */\n date: string;\n}\n\n/**\n * Fetch the latest published template release (`v*` tag).\n *\n * cella publishes two release series — the template (`v*`) and the scaffolder\n * (`create-cella-v*`). We list releases (newest first) and pick the first\n * non-draft, non-prerelease `v*` release so the scaffolder version is ignored.\n */\nexport async function fetchLatestRelease(repositoryUrl: string): Promise<ReleaseInfo | null> {\n const [owner, repo] = parseRepo(repositoryUrl);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=30`;\n\n try {\n const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });\n if (!response.ok) return null;\n\n const releases = (await response.json()) as Array<{\n tag_name?: string;\n published_at?: string;\n draft?: boolean;\n prerelease?: boolean;\n }>;\n\n const release = releases.find((r) => !r.draft && !r.prerelease && /^v\\d/.test(r.tag_name ?? ''));\n if (!release?.tag_name) return null;\n\n return { tag: release.tag_name, date: toDate(release.published_at) };\n } catch {\n return null;\n }\n}\n\n/** Fetch the latest commit on a branch (defaults to 'main'). */\nexport async function fetchLatestCommit(repositoryUrl: string, branch = 'main'): Promise<CommitInfo | null> {\n const [owner, repo] = parseRepo(repositoryUrl);\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`;\n\n try {\n const response = await fetch(apiUrl, { headers: GITHUB_HEADERS });\n if (!response.ok) return null;\n\n const data = (await response.json()) as {\n sha?: string;\n commit?: { message?: string; author?: { date?: string } };\n };\n if (!data.sha) return null;\n\n const message = (data.commit?.message ?? '').split('\\n')[0];\n return { sha: data.sha, shortSha: data.sha.slice(0, 7), message, date: toDate(data.commit?.author?.date) };\n } catch {\n return null;\n }\n}\n","import { readdir } from 'node:fs/promises';\n\n/**\n * Checks if a directory is empty or only contains a .git directory.\n *\n * @param path - The path of the directory to check.\n * @returns Resolves to true if the directory is empty or contains only a .git folder, false otherwise.\n * @throws Throws an error if the path is not a directory or if there's an issue reading the directory.\n */\nexport async function isEmptyDirectory(path: string): Promise<boolean> {\n const files = await readdir(path);\n\n // Check if directory is empty or contains only the .git directory\n return files.length === 0 || (files.length === 1 && files[0] === '.git');\n}\n"],"mappings":"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","names":["existsSync","basename","resolve","pc","readFile","mkdir","dirname","join","relative","resolve","pc","pc","pc","packageManager","fs","pc","pc","fs","resolve","join","pc","readdir","readFile","dirname","join","path","packageManager","mkdir","resolve","relative","rel","join","dirname","pc","readdir","readFile","dirname","join","readdir","path","basename","resolve","existsSync","pc"]}
1
+ {"version":3,"sources":["../src/create-cella-cli.ts","../src/constants.ts","../package.json","../src/create.ts","../src/utils/git/command.ts","../src/add-remote.ts","../src/modules/cli/commands.ts","../src/utils/validate-project-name.ts","../src/modules/cli/display.ts","../src/utils/clean-template.ts","../src/utils/download-template.ts","../src/utils/list-template-files.ts","../src/utils/progress.ts","../src/utils/scan-optional-modules.ts","../src/utils/detect-used-ports.ts","../src/utils/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 { 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 // Display CLI welcome banner\n showWelcome();\n\n // Shared theme to clear prompts after answering\n const promptTheme = { prefix: '', style: { answer: (text: string) => text } };\n const promptContext = { clearPromptOnDone: true };\n\n // Choose which template snapshot to scaffold from: a stable release or the\n // latest commit. Skipped when a custom --template is provided (ref doesn't apply).\n const templateRef = cli.options.template ? undefined : await promptTemplateRef(promptTheme, promptContext);\n\n // Prompt for project name if not provided\n if (!cli.directory) {\n cli.directory = await input(\n {\n message: 'Enter your project name',\n default: 'my-cella-app',\n theme: promptTheme,\n validate: (name) => {\n const validation = validateProjectName(basename(resolve(name)));\n return validation.valid ? true : `Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`;\n },\n },\n promptContext,\n );\n }\n\n const targetFolder = resolve(cli.directory);\n const projectName = basename(targetFolder)?.toLowerCase() || 'project';\n\n // Check if the target folder exists and is not empty\n if (existsSync(targetFolder) && !(await isEmptyDirectory(targetFolder))) {\n const dirName = cli.directory === '.' ? 'Current directory' : `Target directory \"${targetFolder}\"`;\n const message = `${dirName} is not empty. Please choose how you would like to proceed:`;\n\n const action = await select(\n {\n message,\n theme: promptTheme,\n choices: [\n { name: 'Cancel and exit', value: 'cancel' },\n { name: 'Ignore existing files and continue', value: 'ignore' },\n ],\n },\n promptContext,\n );\n\n if (action === 'cancel') {\n process.exit(1);\n }\n }\n\n // Scan sibling directories and prompt for port offset\n const portOffset = cli.options.portOffset ?? (await promptPortOffset(targetFolder, promptTheme, promptContext));\n confirmChoice('Port offset', formatOffset(portOffset));\n\n // Prompt for admin email. Default uses the reserved `example.com` domain (RFC 2606) so the\n // seed address can never resolve or route real mail — it's a placeholder, not a real inbox.\n const adminEmail =\n cli.options.adminEmail ??\n (await input(\n {\n message: 'Admin email for initial seed user',\n default: 'admin@example.com',\n theme: promptTheme,\n },\n promptContext,\n ));\n confirmChoice('Admin email', adminEmail);\n\n // Proceed with the project creation\n const createOptions: CreateOptions = {\n projectName,\n targetFolder,\n packageManager: cli.packageManager,\n templateUrl: cli.options.template,\n templateRef,\n portOffset,\n adminEmail,\n };\n\n await create(createOptions);\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n\n/**\n * Show a short intro with the two starting points (latest release and latest\n * commit, each with its date) and let the user choose which template snapshot to\n * scaffold from. Returns the template ref to check out (release tag or `main`), or\n * undefined 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 'main';\n if (!commit) return release.tag;\n\n // Non-interactive (CI, piped stdin, no TTY): can't render the select — default to the\n // latest release for a stable, reproducible scaffold rather than crashing the prompt.\n if (!process.stdin.isTTY) {\n confirmChoice('Latest release', release.tag);\n return release.tag;\n }\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 // \"Latest commit\" is the tip of main; check out the branch (shallow clones can't\n // reliably fetch an arbitrary SHA over GitHub's smart-HTTP protocol).\n confirmChoice('Latest commit', commit.shortSha);\n return 'main';\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(): string {\n const leftText = `⧈ ${NAME} · ${VERSION}`;\n const left = `${pc.cyan(`⧈ ${NAME}`)} ${pc.dim(`· ${VERSION}`)}`;\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 * Fork config template that replaces `shared/config/config.default.ts` in new forks.\n */\nexport const PLACEHOLDER_CONFIG = 'shared/config/config.template.ts';\n\n/**\n * Read a `.env.example` file and apply key=value replacements.\n * Comments and unmatched keys are preserved as-is.\n * Returns null if the file doesn't exist (caller should generate from scratch).\n */\nexport async function generateEnvFromExample(\n examplePath: string,\n replacements: Record<string, string>,\n): Promise<string | null> {\n const { readFile } = await import('node:fs/promises');\n let content: string;\n try {\n content = await readFile(examplePath, 'utf8');\n } catch {\n return null;\n }\n\n return content.replace(/^([A-Z_][A-Z0-9_]*)=(.*)$/gm, (match, key, _value) => {\n if (key in replacements) return `${key}=${replacements[key]}`;\n return match;\n });\n}\n\n/**\n * Replacement map for `backend/.env`.\n * The backend `.env` is the single source of truth for the project slug, database\n * ports (consumed by `backend/compose.yaml`), connection URLs, admin email and API port.\n */\nexport function getBackendEnvReplacements(\n slug: string,\n adminEmail: string,\n portOffset: number,\n): Record<string, string> {\n const db = 5432 + portOffset;\n return {\n PROJECT_SLUG: slug,\n DB_PORT: String(db),\n DB_TEST_PORT: String(5434 + portOffset),\n DATABASE_URL: `postgres://runtime_role:dev_password@0.0.0.0:${db}/postgres`,\n DATABASE_ADMIN_URL: `postgres://postgres:postgres@0.0.0.0:${db}/postgres`,\n DATABASE_CDC_URL: `postgres://admin_role:dev_password@0.0.0.0:${db}/postgres`,\n ADMIN_EMAIL: adminEmail,\n PORT: String(4000 + portOffset),\n };\n}\n\n/**\n * Generate env config files with project-specific values.\n * All configs go through the same data-driven loop.\n * Values prefixed with '=' are emitted as raw TS expressions (not quoted).\n */\nexport function generateEnvConfigs(slug: string, name: string, portOffset: number): Record<string, string> {\n const fe = 3000 + portOffset;\n const be = 4000 + portOffset;\n\n const header =\n \"import type { DeepPartial } from '../src/config-builder/types';\\nimport type _default from './config.default';\\n\";\n\n // Per-environment specs: optional imports + object props (= prefix → raw TS expression)\n const envs: Record<string, { imports?: string; props: Record<string, string | boolean> }> = {\n development: {\n props: {\n slug: `${slug}-development`,\n domain: '',\n frontendUrl: `http://localhost:${fe}`,\n backendUrl: `http://localhost:${be}`,\n backendAuthUrl: `http://localhost:${be}/auth`,\n },\n },\n staging: {\n props: {\n slug: `${slug}-staging`,\n domain: `${slug}.example.com`,\n frontendUrl: `https://staging.${slug}.example.com`,\n backendUrl: `https://api-staging.${slug}.example.com`,\n backendAuthUrl: `https://api-staging.${slug}.example.com/auth`,\n },\n },\n tunnel: {\n props: {\n frontendUrl: `https://localhost:${fe}`,\n backendUrl: `https://${slug}.ngrok.dev`,\n backendAuthUrl: `https://${slug}.ngrok.dev/auth`,\n },\n },\n test: {\n imports: \"import development from './config.development';\\n\",\n props: {\n domain: '',\n frontendUrl: '=development.frontendUrl',\n backendUrl: '=development.backendUrl',\n backendAuthUrl: '=development.backendAuthUrl',\n },\n },\n production: { props: { maintenance: false } },\n };\n\n // Serialize value: '=' prefix → raw TS expression, boolean → literal, string → quoted\n const lit = (v: string | boolean) => {\n if (typeof v === 'boolean') return String(v);\n if (v.startsWith('=')) return v.slice(1);\n return `'${v}'`;\n };\n\n const result: Record<string, string> = {};\n\n for (const [mode, { imports = '', props }] of Object.entries(envs)) {\n const nameEntry = mode !== 'production' ? ` name: '${name} ${mode.toUpperCase()}',\\n` : '';\n const body = Object.entries(props)\n .map(([k, v]) => ` ${k}: ${lit(v)},`)\n .join('\\n');\n result[`./shared/config/config.${mode}.ts`] =\n `${imports}${header}\\nexport default {\\n mode: '${mode}',\\n${nameEntry}${body}\\n} satisfies DeepPartial<typeof _default>;\\n`;\n }\n\n return result;\n}\n","{\n \"name\": \"@cellajs/create-cella\",\n \"version\": \"0.3.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/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 \"dist\",\n \"index.js\",\n \"README.md\"\n ],\n \"imports\": {\n \"#/*\": \"./src/*\"\n },\n \"bin\": {\n \"create-cella\": \"index.js\"\n },\n \"scripts\": {\n \"start\": \"tsx ./src/create-cella-cli.ts\",\n \"try\": \"tsx ./src/create-cella-cli.ts ../cella-try-app\",\n \"clean\": \"rimraf ./dist\",\n \"build\": \"tsup\",\n \"test-build\": \"pnpm run build && node index.js\",\n \"prepublishOnly\": \"pnpm run test:release\",\n \"check\": \"pnpm ts && pnpm biome check --write .\",\n \"ts\": \"tsgo --pretty\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"test\": \"vitest run\",\n \"test:release\": \"pnpm run build && vitest run --config vitest.release.config.ts\",\n \"test:watch\": \"vitest\",\n \"prepare\": \"lefthook install\"\n },\n \"dependencies\": {\n \"@inquirer/prompts\": \"^8.5.2\",\n \"commander\": \"^15.0.0\",\n \"ignore\": \"^7.0.5\",\n \"isomorphic-git\": \"^1.38.5\",\n \"ora\": \"^9.4.1\",\n \"picocolors\": \"^1.1.1\",\n \"validate-npm-package-name\": \"^8.0.0\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.5.1\",\n \"@commitlint/cli\": \"^21.1.0\",\n \"@commitlint/config-conventional\": \"^21.1.0\",\n \"@evilmartians/lefthook\": \"^2.1.9\",\n \"@types/node\": \"24.10.13\",\n \"@types/validate-npm-package-name\": \"^4.0.2\",\n \"@typescript/native-preview\": \"7.0.0-dev.20260628.1\",\n \"rimraf\": \"^6.1.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.22.4\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.9\"\n },\n \"packageManager\": \"pnpm@11.8.0\",\n \"commitlint\": {\n \"extends\": [\n \"@commitlint/config-conventional\"\n ],\n \"rules\": {\n \"type-enum\": [\n 2,\n \"always\",\n [\n \"feat\",\n \"fix\",\n \"perf\",\n \"refactor\",\n \"revert\",\n \"docs\",\n \"chore\",\n \"build\",\n \"ci\",\n \"style\",\n \"test\"\n ]\n ]\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { cp, mkdir, rm } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { checkbox } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { addRemote } from '#/add-remote';\nimport { TEMPLATE_URL } from '#/constants';\nimport { type CreateOptions, confirmChoice, showSuccess } from '#/modules/cli';\nimport { cleanTemplate } from '#/utils/clean-template';\nimport { downloadGithubTemplate } from '#/utils/download-template';\nimport { gitAddAll, gitCommit, gitInit } from '#/utils/git';\nimport { listTemplateFiles } from '#/utils/list-template-files';\nimport { createProgress, pauseSpinner, resumeSpinner } from '#/utils/progress';\nimport { optionalModuleFolders, scanOptionalModules } from '#/utils/scan-optional-modules';\n\n/** Check if a path is a local directory */\nfunction isLocalPath(path: string): boolean {\n return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');\n}\n\nfunction shouldSkipStep(name: 'git' | 'remote'): boolean {\n return process.env[`CREATE_CELLA_SKIP_${name.toUpperCase()}`] === 'true';\n}\n\nexport async function create({\n projectName,\n targetFolder,\n packageManager,\n templateUrl,\n templateRef,\n portOffset,\n adminEmail,\n silent = false,\n}: CreateOptions): Promise<void> {\n // Save the original working directory\n const originalCwd = process.cwd();\n\n // Use custom template or default\n const template = templateUrl || TEMPLATE_URL;\n const isLocalTemplate = templateUrl && isLocalPath(templateUrl);\n\n const progress = createProgress('creating project', silent);\n\n try {\n // Create the target folder\n progress.step('creating project folder');\n await mkdir(targetFolder, { recursive: true });\n process.chdir(targetFolder);\n\n // Download or copy the template\n if (isLocalTemplate) {\n progress.step('copying local template');\n const sourcePath = resolve(originalCwd, templateUrl);\n\n // Check if target is inside source (would cause EINVAL). Use a path-boundary check\n // (not a string prefix) so siblings like `cella-try-app` next to `cella` aren't\n // misdetected as being inside the template.\n const rel = relative(sourcePath, targetFolder);\n const targetInsideTemplate = rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n if (targetInsideTemplate) {\n throw new Error(\n 'Cannot create project inside the template directory.\\n' +\n ` Run from outside: cd ~ && pnpm create @cellajs/cella ${projectName} --template ${templateUrl}`,\n );\n }\n\n // Copy only what git would track (respects .gitignore: no build output, no local .env secrets).\n const tracked = await listTemplateFiles(sourcePath);\n for (const rel of tracked) {\n const src = join(sourcePath, rel);\n if (!existsSync(src)) continue; // listed but deleted in the working tree\n const dest = join(targetFolder, rel);\n await mkdir(dirname(dest), { recursive: true });\n await cp(src, dest);\n }\n } else {\n // Pin to the chosen ref (release tag or commit SHA), else the default branch.\n const ref = templateRef ?? 'main';\n progress.step(`downloading cella template${templateRef ? ` (${templateRef})` : ''}`);\n await downloadGithubTemplate(template, ref, targetFolder);\n }\n\n // Ask which optional modules to keep, then drop deselected paths\n if (!silent) await promptOptionalModules(targetFolder);\n\n // Clean the template, generate configs\n progress.step('cleaning template');\n const displayName = projectName.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });\n\n // Initialize git repository\n const gitFolderPath = join(targetFolder, '.git');\n if (!shouldSkipStep('git') && !existsSync(gitFolderPath)) {\n progress.step('initializing git');\n await gitInit(targetFolder);\n await gitAddAll(targetFolder);\n await gitCommit(targetFolder, 'Initial commit');\n }\n\n // Add upstream remote\n if (!shouldSkipStep('remote')) {\n progress.step('adding upstream remote');\n await addRemote({ targetFolder, silent: true });\n }\n\n // Done\n progress.done(pc.bold(`created ${projectName}`));\n } catch (error) {\n progress.fail(error instanceof Error ? error.message : String(error));\n // Rethrow so callers (CLI entry, tests) see the real failure. The CLI entry\n // turns this into a non-zero exit; swallowing it here with process.exit hid\n // the underlying error from CI and vitest.\n throw error;\n }\n\n // Check if the working directory needs to be changed\n const needsCd = originalCwd !== targetFolder;\n const relativePath = relative(originalCwd, targetFolder);\n\n // Display final success message\n showSuccess(projectName, targetFolder, relativePath, needsCd, packageManager);\n}\n\n/**\n * Scan the downloaded template for optional modules and let the user deselect them.\n * Deselected modules have their whole folder removed before cleaning. Default keeps\n * everything (parity with previous behavior).\n */\nasync function promptOptionalModules(targetFolder: string): Promise<void> {\n const modules = await scanOptionalModules(targetFolder);\n if (modules.length === 0) return;\n\n // Non-interactive (CI, piped stdin, no TTY): can't render a checkbox — keep every module\n // rather than crashing with \"User force closed the prompt\". Matches the default-keep behavior.\n if (!process.stdin.isTTY) return;\n\n pauseSpinner();\n // Blank line to separate the preceding progress step from this prompt.\n console.info();\n const keep = await checkbox(\n {\n message: 'Optional modules to include',\n // Leading space on the radio icons adds a gap after the cursor (inquirer renders\n // `${cursor}${checkbox} ${name}` with no space between cursor and checkbox).\n theme: { icon: { checked: ` ${pc.green('◉')}`, unchecked: ' ◯' } },\n choices: modules.map((m) => ({\n name: `${m.name} ${pc.dim(`· ${m.description}`)}`,\n value: m.name,\n checked: true,\n })),\n },\n // Clear the answered prompt so we can print a compact, non-bold summary that lists\n // only the kept module names (not their descriptions).\n { clearPromptOnDone: true },\n );\n\n // Keep the choice visible after the prompt clears: plain label plus the selected\n // module names only (not their descriptions), or \"none\" when everything was deselected.\n confirmChoice('Optional modules', keep.length ? keep.join(', ') : pc.dim('none'));\n resumeSpinner();\n\n // Remove every folder owned by a deselected module. `optionalModuleFolders` lists more\n // route variants than any single module uses; `force: true` ignores the ones absent here.\n const folders = modules.filter((m) => !keep.includes(m.name)).flatMap(optionalModuleFolders);\n await Promise.all(folders.map((f) => rm(join(targetFolder, f), { recursive: true, force: true })));\n}\n","import fs from 'node:fs';\nimport git from 'isomorphic-git';\n\n/**\n * Git helpers backed by isomorphic-git (pure JS) instead of shelling out to the\n * `git` binary. This keeps `create-cella` free of `child_process` (no shell access)\n * and removes any dependency on the user's global git config — the initial commit\n * uses an explicit author, so scaffolding never fails with \"Author identity unknown\".\n */\n\n/** Author used for the scaffold's initial commit (users re-author their own commits later). */\nconst INITIAL_AUTHOR = { name: 'cella', email: 'info@cellajs.com' } as const;\n\n/**\n * Initializes a new Git repository in the specified directory.\n */\nexport async function gitInit(repoPath: string): Promise<void> {\n await git.init({ fs, dir: repoPath, defaultBranch: 'main' });\n}\n\n/**\n * Stages all files in the repository (respects `.gitignore`).\n */\nexport async function gitAddAll(repoPath: string): Promise<void> {\n await git.add({ fs, dir: repoPath, filepath: '.' });\n}\n\n/**\n * Creates a commit with the specified message and returns the commit SHA.\n */\nexport async function gitCommit(repoPath: string, message: string): Promise<string> {\n return git.commit({ fs, dir: repoPath, message, author: INITIAL_AUTHOR });\n}\n\n/**\n * Gets the URL of a remote. Throws when the remote does not exist (mirrors\n * `git remote get-url` failing) so callers can detect a missing remote.\n */\nexport async function gitRemoteGetUrl(repoPath: string, remoteName: string): Promise<string> {\n const remotes = await git.listRemotes({ fs, dir: repoPath });\n const match = remotes.find((r) => r.remote === remoteName);\n if (!match) throw new Error(`No such remote '${remoteName}'`);\n return match.url;\n}\n\n/**\n * Adds a new remote.\n */\nexport async function gitRemoteAdd(repoPath: string, remoteName: string, remoteUrl: string): Promise<void> {\n await git.addRemote({ fs, dir: repoPath, remote: remoteName, url: remoteUrl });\n}\n\n/**\n * Removes a remote.\n */\nexport async function gitRemoteRemove(repoPath: string, remoteName: string): Promise<void> {\n await git.deleteRemote({ fs, dir: repoPath, remote: remoteName });\n}\n","import { CELLA_REMOTE_URL } from '#/constants';\nimport type { AddRemoteOptions } from '#/modules/cli';\nimport { gitRemoteAdd, gitRemoteGetUrl, gitRemoteRemove } from '#/utils/git';\n\n/**\n * Adds or updates the upstream remote for the Cella template.\n * @param options - Configuration options\n * @param options.silent - If true, don't throw on failure (used when called from progress tracker)\n */\nexport async function addRemote({\n targetFolder,\n remoteUrl = CELLA_REMOTE_URL,\n remoteName = 'upstream',\n silent = false,\n}: AddRemoteOptions): Promise<void> {\n try {\n // Check if the remote exists\n let remote: string | null = null;\n\n try {\n remote = await gitRemoteGetUrl(targetFolder, remoteName);\n } catch {\n // If the remote doesn't exist, it throws a fatal error\n remote = null;\n }\n\n // Add or update the remote if it doesn't exist or differs from `remoteUrl`\n if (!remote) {\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n } else if (remote !== remoteUrl) {\n // Remove existing remote and set the new URL\n await gitRemoteRemove(targetFolder, remoteName);\n await gitRemoteAdd(targetFolder, remoteName, remoteUrl);\n }\n } catch (error) {\n if (!silent) {\n throw error;\n }\n }\n}\n","import { basename, resolve } from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { NAME, VERSION } from '#/constants';\nimport { validateProjectName } from '#/utils/validate-project-name';\nimport type { CLIConfig, CLIOptions } from './types';\n\nfunction parsePortOffset(value: string): number {\n const parsed = Number(value);\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 490) {\n throw new InvalidArgumentError('Port offset must be an integer between 0 and 490');\n }\n\n if (parsed % 10 !== 0) {\n throw new InvalidArgumentError('Port offset must be a multiple of 10');\n }\n\n return parsed;\n}\n\n// Initialize CLI variables\nlet directory: string | null = null;\nconst packageManager = 'pnpm';\n\n/**\n * Defines the root CLI command using Commander.\n * This command accepts CLI options and validates user input.\n */\nexport const command = new Command(NAME)\n .version(VERSION, '-v, --version', `output the current version of ${NAME}`)\n .argument('[directory]', 'the directory name for the new project')\n .usage('[directory] [options]')\n .helpOption('-h, --help', 'display this help message')\n .option('--template <path>', 'use a custom template (local path or github:user/repo)')\n .option('--port-offset <number>', 'set the port offset (0-490 in steps of 10)', parsePortOffset)\n .option('--admin-email <email>', 'set the admin email for the initial seed user')\n .action((name: string) => {\n const trimmedName = typeof name === 'string' ? name.trim() : name;\n\n if (trimmedName) {\n const validation = validateProjectName(basename(resolve(trimmedName)));\n\n if (!validation.valid) {\n throw new InvalidArgumentError(`Invalid project name: ${validation.problems?.[0] ?? 'unknown error'}`);\n }\n\n directory = trimmedName;\n }\n })\n .parse();\n\n// Gather the CLI options and arguments\nconst options: CLIOptions = command.opts<CLIOptions>();\n\n/**\n * Runs the CLI and returns the parsed configuration.\n * This function parses command line arguments and returns the CLI config.\n */\nexport function runCli(): CLIConfig {\n return {\n options,\n args: command.args,\n directory,\n packageManager,\n };\n}\n\n// Export CLI configuration for direct import\nexport const cli: CLIConfig = runCli();\n","import validate from 'validate-npm-package-name';\n\ninterface ValidationResult {\n valid: boolean;\n problems?: string[];\n}\n\nexport function validateProjectName(name: string): ValidationResult {\n const nameValidation = validate(name);\n\n if (nameValidation.validForNewPackages) {\n return { valid: true };\n }\n\n return {\n valid: false,\n problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])],\n };\n}\n","import pc from 'picocolors';\nimport { DESCRIPTION, DIVIDER, getHeaderLine } from '#/constants';\n\n/** ASCII art logo for the CLI welcome screen. */\nfunction showAscii(): void {\n console.info(pc.cyan(' _ _ '));\n console.info(pc.cyan('▒▓█████▓▒ ___ ___| | | __ _ '));\n console.info(pc.cyan('▒▓█ █▓▒ / __/ _ \\\\ | |/ _` | '));\n console.info(pc.cyan('▒▓█ █▓▒ | (_| __/ | | (_| | '));\n console.info(pc.cyan('▒▓█████▓▒ \\\\___\\\\___|_|_|\\\\__,_| '));\n}\n\n/**\n * Displays the compact CLI welcome header.\n */\nexport function showWelcome(): void {\n console.info();\n showAscii();\n console.info();\n console.info(pc.dim(DESCRIPTION));\n console.info();\n console.info(getHeaderLine());\n console.info(DIVIDER);\n}\n\n/**\n * Print a cleared prompt's outcome as a persistent confirmation line:\n * `✓ <label> · <value>`, followed by a blank separator line. Used after prompts\n * that clear themselves on done so the chosen value stays visible.\n */\nexport function confirmChoice(label: string, value: string): void {\n console.info(`${pc.green('✓')} ${label} · ${value}`);\n console.info();\n}\n\n/**\n * Displays the final success message after project creation.\n */\nexport function showSuccess(\n projectName: string,\n _targetFolder: string,\n relativePath: string,\n needsCd: boolean,\n packageManager: string,\n): void {\n console.info();\n\n console.info(pc.bold('Next steps'));\n console.info(DIVIDER);\n console.info();\n\n if (needsCd) {\n console.info(`${pc.dim('$')} cd ${pc.cyan(relativePath)}`);\n }\n console.info(`${pc.dim('$')} ${pc.cyan(`${packageManager} install`)}`);\n console.info(`${pc.dim('$')} ${pc.cyan(`${packageManager} generate`)}`);\n console.info(\n `${pc.dim('$')} ${pc.cyan(`${packageManager} docker`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} seed`)} ${pc.dim('&&')} ${pc.cyan(`${packageManager} dev`)}`,\n );\n console.info();\n\n // Point to the generated README for first-run details and sign-in credentials. Use the\n // path relative to the new project so the terminal linkifies the right file (not this CLI's).\n const readmePath = relativePath ? `${relativePath}/README.md` : 'README.md';\n console.info(`${pc.gray('To get started building, please read:')} ${pc.cyan(readmePath)}`);\n console.info();\n console.info(`🥳 enjoy building ${pc.green(projectName)} with cella!`);\n console.info();\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport 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 * 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, displayName);\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. Skip silently if the folder doesn't exist —\n // the template may not include every optional folder (e.g. backend/drizzle).\n let files: string[];\n try {\n files = await fs.readdir(folderPath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n\n // `recursive: true` handles nested folders; `force: true` ignores races/missing entries.\n await Promise.all(files.map((file) => fs.rm(path.join(folderPath, file), { recursive: true, force: true })));\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 fork config template shipped inside the cloned Cella template\n * (`shared/config/config.template.ts`), interpolate project tokens, write the\n * result as `shared/config/config.default.ts` — replacing the original — and\n * delete the now-consumed template from the fork.\n */\nasync function applyPlaceholderConfig(targetFolder: string, projectName: string, displayName: string): Promise<void> {\n const src = path.resolve(targetFolder, PLACEHOLDER_CONFIG);\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 await fs.rm(src, { force: true });\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.info(`\\n${warningMark} Fork config template \"${src}\" not found > Skip`);\n } else {\n throw err;\n }\n }\n}\n","import fs from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport git from 'isomorphic-git';\nimport http from 'isomorphic-git/http/node';\n\n/** Parse `github:owner/repo` or `owner/repo` into its owner and repo parts. */\nfunction parseGithubRepo(template: string): { owner: string; repo: string } {\n const [owner, repo] = template.replace(/^github:/, '').split('/');\n if (!owner || !repo) {\n throw new Error(`Invalid template '${template}'. Expected 'github:owner/repo'.`);\n }\n return { owner, repo };\n}\n\n/**\n * Clones a GitHub template into `targetFolder` using isomorphic-git (pure JS).\n *\n * A shallow, single-branch clone over HTTPS — no `git` binary, no `child_process`,\n * so `create-cella` keeps a clean supply-chain profile (no shell access). Unlike a\n * raw tarball parse, isomorphic-git reconstructs the tree itself, so file modes and\n * long paths are always correct. The cloned `.git` history is discarded afterwards;\n * scaffolding re-initialises the repo with a fresh initial commit.\n *\n * @param template - `github:owner/repo` (or `owner/repo`).\n * @param ref - Branch or tag to check out (e.g. `main` or a release tag).\n * @param targetFolder - Directory to clone the template into.\n */\nexport async function downloadGithubTemplate(template: string, ref: string, targetFolder: string): Promise<void> {\n const { owner, repo } = parseGithubRepo(template);\n const url = `https://github.com/${owner}/${repo}.git`;\n\n await git.clone({ fs, http, dir: targetFolder, url, ref, singleBranch: true, depth: 1 });\n\n // Drop the cloned history — the scaffold gets its own fresh initial commit.\n await rm(join(targetFolder, '.git'), { recursive: true, force: true });\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport ignore from 'ignore';\n\n/**\n * Lists the files under `root` that git would track, without shelling out to git.\n *\n * Honors the root `.gitignore` (via the `ignore` package) and always skips `.git`\n * and `node_modules`. Used for the local-template dev flow (`--template ./local`)\n * so build output and local `.env` secrets aren't copied into the new project.\n *\n * Paths are returned relative to `root` using POSIX separators.\n */\nexport async function listTemplateFiles(root: string): Promise<string[]> {\n const ig = ignore().add(['.git', 'node_modules']);\n try {\n ig.add(await readFile(join(root, '.gitignore'), 'utf8'));\n } catch {\n // No .gitignore at the template root — only the defaults above apply.\n }\n\n const files: string[] = [];\n\n async function walk(relDir: string): Promise<void> {\n const entries = await readdir(relDir ? join(root, relDir) : root, { withFileTypes: true });\n for (const entry of entries) {\n const rel = relDir ? `${relDir}/${entry.name}` : entry.name;\n // `ignore` matches directory rules when the path ends with a slash.\n if (ig.ignores(entry.isDirectory() ? `${rel}/` : rel)) continue;\n if (entry.isDirectory()) await walk(rel);\n else if (entry.isFile()) files.push(rel);\n }\n }\n\n await walk('');\n return files;\n}\n","/**\n * Progress Tracker Utility\n *\n * Provides compact progress tracking for CLI operations.\n * Uses a single spinner that updates in place.\n */\n\nimport ora, { type Ora } from 'ora';\nimport pc from 'picocolors';\n\n/** Global reference to the currently active spinner */\nlet activeSpinner: Ora | null = null;\n\n/** Pause the active spinner (for interactive prompts) */\nexport function pauseSpinner(): void {\n if (activeSpinner) {\n activeSpinner.stop();\n }\n}\n\n/** Resume the active spinner after a prompt */\nexport function resumeSpinner(): void {\n if (activeSpinner) {\n activeSpinner.start();\n }\n}\n\nexport interface ProgressTracker {\n /** Update the current step (shown as spinner text) */\n step: (message: string) => void;\n /** Mark the tracker as complete with final message */\n done: (message: string) => void;\n /** Mark the tracker as failed with error message */\n fail: (message: string) => void;\n}\n\n/**\n * Creates a progress tracker for a multi-step operation.\n * Shows a single spinner that updates in place.\n *\n * @param title - The initial title for the spinner\n * @returns A ProgressTracker instance\n *\n * @example\n * const progress = createProgress('creating project');\n * progress.step('downloading template');\n * progress.step('installing dependencies');\n * progress.done('project created');\n */\nexport function createProgress(title: string, silent = false): ProgressTracker {\n const completedSteps: string[] = [];\n\n const spinner = ora({\n text: title,\n isSilent: silent,\n });\n\n activeSpinner = spinner;\n spinner.start();\n\n // Helper to log (respects silent mode)\n const log = (msg: string) => {\n if (!silent) console.info(msg);\n };\n\n return {\n step: (message: string) => {\n // Complete previous step with green check if there was one\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n }\n completedSteps.push(message);\n spinner.text = message;\n spinner.start();\n },\n\n done: (message: string) => {\n // Complete the last step with green check\n if (completedSteps.length > 0) {\n spinner.stop();\n log(`${pc.green('✓')} ${completedSteps[completedSteps.length - 1]}`);\n } else {\n spinner.stop();\n }\n activeSpinner = null;\n if (message) {\n log('');\n log(`${pc.green('✓')} ${message}`);\n }\n },\n\n fail: (message: string) => {\n spinner.stop();\n activeSpinner = null;\n log(`${pc.red('✗')} ${pc.red(message)}`);\n },\n };\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\n\n/** An optional module discovered in a `*-module.ts` registration. */\nexport interface OptionalModule {\n name: string;\n description: string;\n /** Module folder, relative to the project root, removed when deselected. */\n folder: string;\n}\n\n/** Directories never worth scanning. */\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage']);\n\n/**\n * Scan the template for `*-module.ts` files that register `optional: true` and return\n * their name, description and folder. Pure static regex parsing — no imports/eval — so it\n * runs before dependencies are installed. The folder is the module file's directory.\n */\nexport async function scanOptionalModules(targetFolder: string): Promise<OptionalModule[]> {\n const files: string[] = [];\n await collectModuleFiles(targetFolder, files);\n\n const modules: OptionalModule[] = [];\n for (const file of files) {\n const src = await readFile(file, 'utf8');\n if (!/optional:\\s*true/.test(src)) continue;\n\n const name = src.match(/name:\\s*['\"]([^'\"]+)['\"]/)?.[1];\n const description = src.match(/description:\\s*['\"]([^'\"]+)['\"]/)?.[1] ?? '';\n if (!name) continue;\n\n modules.push({ name, description, folder: relative(targetFolder, dirname(file)) });\n }\n return modules.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Every project-root-relative folder removed when an optional module is deselected: its own\n * module folder plus the route and static-asset folders it may own. Route folders are\n * speculative — a module owns either a pathless `_<name>` (URL-transparent) or a path-based\n * `<name>` route, under `routes/_public` and/or `routes/_app` — so callers remove only the\n * variants that actually exist. Single-sourced here so removal and tests stay in sync.\n */\nexport function optionalModuleFolders({ name, folder }: OptionalModule): string[] {\n return [\n folder,\n `frontend/src/routes/_public/_${name}`,\n `frontend/src/routes/_public/${name}`,\n `frontend/src/routes/_app/_${name}`,\n `frontend/src/routes/_app/${name}`,\n `frontend/public/static/${name}`,\n ];\n}\n\nasync function collectModuleFiles(dir: string, out: string[]): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await collectModuleFiles(join(dir, entry.name), out);\n } else if (entry.name.endsWith('-module.ts')) {\n out.push(join(dir, entry.name));\n }\n }\n}\n","import { readdir, readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\ninterface UsedPorts {\n project: string;\n frontend: number;\n backend: number;\n offset: number;\n}\n\n/**\n * Scan sibling directories for existing cella forks and extract their dev ports.\n * Looks for `shared/config/config.development.ts` to identify cella-based projects.\n */\nexport async function detectUsedPorts(targetFolder: string): Promise<UsedPorts[]> {\n const parentDir = dirname(targetFolder);\n const used: UsedPorts[] = [];\n\n let siblings: string[];\n try {\n siblings = await readdir(parentDir);\n } catch {\n return used;\n }\n\n for (const name of siblings) {\n const configPath = join(parentDir, name, 'shared/config/config.development.ts');\n try {\n const content = await readFile(configPath, 'utf8');\n const feMatch = content.match(/frontendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n const beMatch = content.match(/backendUrl:\\s*'http:\\/\\/localhost:(\\d+)'/);\n if (feMatch && beMatch) {\n const frontend = Number(feMatch[1]);\n const backend = Number(beMatch[1]);\n used.push({\n project: name,\n frontend,\n backend,\n offset: frontend - 3000,\n });\n }\n } catch {\n // Not a cella fork, skip\n }\n }\n\n return used;\n}\n\n/** Find the next available offset (in steps of 10) that doesn't collide with existing forks. */\nexport function findNextOffset(usedPorts: UsedPorts[]): number {\n const usedOffsets = new Set(usedPorts.map((p) => p.offset));\n for (let offset = 0; offset <= 490; offset += 10) {\n if (!usedOffsets.has(offset)) return offset;\n }\n return 0;\n}\n","/**\n * 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","rm","dirname","join","relative","resolve","pc","pc","pc","packageManager","fs","pc","pc","resolve","fs","fs","git","join","pc","readdir","readFile","join","path","packageManager","resolve","relative","rel","join","dirname","pc","rm","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.3.0",
3
+ "version": "0.3.2",
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.",
@@ -19,7 +19,6 @@
19
19
  "type": "module",
20
20
  "main": "./dist/index.js",
21
21
  "files": [
22
- "configs",
23
22
  "dist",
24
23
  "index.js",
25
24
  "README.md"
@@ -35,7 +34,6 @@
35
34
  "commander": "^15.0.0",
36
35
  "ignore": "^7.0.5",
37
36
  "isomorphic-git": "^1.38.5",
38
- "nanotar": "^0.3.0",
39
37
  "ora": "^9.4.1",
40
38
  "picocolors": "^1.1.1",
41
39
  "validate-npm-package-name": "^8.0.0"
@@ -1,402 +0,0 @@
1
- /**
2
- * Placeholder default config for new forks created by `create-cella`.
3
- *
4
- * This file replaces `shared/config/config.default.ts` during project creation.
5
- * Tokens `__project_name__` and `__project_slug__` are interpolated with the
6
- * project name chosen by the user. All Cella-specific branding, URLs, tokens
7
- * and company details are replaced with safe placeholder values.
8
- *
9
- * NOTE: This file is NOT compiled or imported — it is copied and interpolated
10
- * as plain text. The import path below is relative to `shared/config/` (its destination).
11
- */
12
- import type { ConfigMode, RequiredConfig, S3ConfigInput } from '../src/config-builder/types';
13
-
14
- // Re-export for external consumers
15
- export { roles, hierarchy } from './hierarchy-config';
16
-
17
- // Set these early for reuse
18
- const entityTypes = ['user', 'organization', 'attachment', 'page'] as const;
19
- const productEntityTypes = ['attachment', 'page'] as const;
20
-
21
- export const config = {
22
-
23
- /******************************************************************************
24
- * ENTITY DATA MODEL
25
- ******************************************************************************/
26
-
27
- /** All entity types in the app - must match hierarchy.allTypes. */
28
- entityTypes,
29
-
30
- /** Context entities with memberships - must match hierarchy.contextTypes. */
31
- contextEntityTypes: ['organization'] as const,
32
-
33
- /** Product/content entities - must match hierarchy.productTypes. */
34
- productEntityTypes,
35
-
36
- /**
37
- * Product entity types tracked for seen/unseen counts.
38
- * Unseen counts are grouped by the parent context entity of each tracked type.
39
- */
40
- seenTrackedEntityTypes: ['attachment'] as const,
41
-
42
- /** Maps entity types to their ID column names - must match entityTypes */
43
- entityIdColumnKeys: {
44
- user: 'userId',
45
- organization: 'organizationId',
46
- attachment: 'attachmentId',
47
- page: 'pageId',
48
- } as const,
49
-
50
- /** Available CRUD actions for permission checks */
51
- entityActions: ['create', 'read', 'update', 'delete'] as const,
52
-
53
- /** Resource types that are not entities but have activities logged */
54
- resourceTypes: ['request', 'membership', 'inactive_membership', 'tenant'] as const,
55
-
56
- /**
57
- * Entity embeddings: declares which entities are embedded as ID arrays inside
58
- * other entities. Forks extend when adding new embedding relationships.
59
- */
60
- entityEmbeddings: [] as readonly {
61
- readonly embeddedEntity: (typeof productEntityTypes)[number];
62
- readonly hostEntity: (typeof productEntityTypes)[number];
63
- readonly hostColumn: string;
64
- }[],
65
-
66
- /**
67
- * User menu structure of context entities with optional nested subentities.
68
- * If subentityType is set, the table must include `${entity}Id` foreign key.
69
- */
70
- menuStructure: [
71
- { entityType: 'organization', subentityType: null } as const,
72
- ],
73
-
74
- /** Default restrictions for tenants (entity quotas and rate limits) */
75
- defaultRestrictions: {
76
- quotas: {
77
- organization: 5,
78
- user: 1000,
79
- attachment: 100,
80
- },
81
- rateLimits: {
82
- apiPointsPerHour: 1000,
83
- },
84
- } as const,
85
-
86
- /******************************************************************************
87
- * SYSTEM ROLES
88
- ******************************************************************************/
89
-
90
- systemRoles: ['admin'] as const,
91
-
92
- /******************************************************************************
93
- * APP IDENTITY
94
- ******************************************************************************/
95
-
96
- /** App display name shown in UI and emails */
97
- name: '__project_name__',
98
- /** URL-safe identifier used in paths and storage */
99
- slug: '__project_slug__',
100
- /** Primary domain for the app */
101
- domain: '__project_slug__.example.com',
102
- /** App description for SEO and meta tags */
103
- description: '__project_name__ — powered by Cella.',
104
- /** SEO keywords for search engines */
105
- keywords:
106
- 'starter kit, fullstack, monorepo, typescript, hono, honojs, drizzle, baseui, react, postgres, pwa, offline, instant updates, realtime data, sync engine',
107
-
108
- /******************************************************************************
109
- * URLS & ENDPOINTS
110
- ******************************************************************************/
111
-
112
- /** Frontend SPA base URL */
113
- frontendUrl: 'https://__project_slug__.example.com',
114
- /** Backend API base URL */
115
- backendUrl: 'https://api.__project_slug__.example.com',
116
- /** OAuth callback base URL */
117
- backendAuthUrl: 'https://api.__project_slug__.example.com/auth',
118
- /** Yjs realtime relay URL */
119
- yjsUrl: 'wss://yjs.__project_slug__.example.com',
120
- /** AI service base URL */
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,
138
-
139
- /** About page URL */
140
- aboutUrl: '/about',
141
- /** Status page URL for uptime monitoring */
142
- statusUrl: 'https://status.__project_slug__.example.com',
143
- /** Canonical production URL */
144
- productionUrl: 'https://__project_slug__.example.com',
145
-
146
- /** Default redirect path after login */
147
- defaultRedirectPath: '/home',
148
- /** Redirect path for first-time users */
149
- welcomeRedirectPath: '/welcome',
150
-
151
- /******************************************************************************
152
- * EMAIL
153
- ******************************************************************************/
154
-
155
- /** From address for system notifications */
156
- senderEmail: 'notifications@__project_slug__.example.com',
157
- /** Email address for user support inquiries */
158
- supportEmail: 'support@__project_slug__.example.com',
159
- /** Receive security warnings */
160
- securityEmail: 'security@__project_slug__.example.com',
161
-
162
- /******************************************************************************
163
- * MODE & FLAGS
164
- ******************************************************************************/
165
-
166
- /** Runtime mode - overridden per environment file */
167
- mode: 'development' as ConfigMode,
168
- /** Enable maintenance mode (blocks all requests) */
169
- maintenance: false,
170
- /** Cookie version - increment when changing cookie structure to invalidate old cookies */
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',
174
-
175
- /******************************************************************************
176
- * FEATURE FLAGS
177
- ******************************************************************************/
178
-
179
- /**
180
- * Feature toggles for app capabilities.
181
- * Use to enable/disable major features without code changes.
182
- */
183
- has: {
184
- /** Progressive Web App support for preloading static assets and offline support */
185
- pwa: true as boolean,
186
- /** Allow users to sign up. If false, the app is by invitation only */
187
- selfRegistration: false as boolean,
188
- /** Suggest a waitlist for unknown emails when sign up is disabled */
189
- waitlist: false as boolean,
190
- /** S3 fully configured - if false, files will be stored in local browser (IndexedDB) */
191
- uploadEnabled: false as boolean,
192
- /** Customer support chat widget (Gleap). Unrelated to the AI module. */
193
- chatSupport: false as boolean,
194
- },
195
-
196
- /******************************************************************************
197
- * AUTHENTICATION
198
- ******************************************************************************/
199
-
200
- /**
201
- * Enabled authentication strategies.
202
- * TOTP can only be used as MFA fallback with passkey as primary.
203
- */
204
- enabledAuthStrategies: ['passkey', 'oauth', 'totp', 'magic'] as const,
205
-
206
- /** Enabled OAuth providers - currently supports: github, google, microsoft */
207
- enabledOAuthProviders: ['github'] as const,
208
-
209
- /** Token types used for verification flows */
210
- tokenTypes: ['email-verification', 'oauth-verification', 'invitation', 'confirm-mfa', 'magic'] as const,
211
-
212
- /** TOTP configuration for MFA */
213
- totpConfig: {
214
- intervalInSeconds: 30,
215
- gracePeriodInSeconds: 60,
216
- digits: 6,
217
- },
218
-
219
- /******************************************************************************
220
- * API CONFIGURATION
221
- ******************************************************************************/
222
-
223
- /** API version prefix for endpoints */
224
- apiVersion: 'v1',
225
- /** API documentation description shown in Scalar */
226
- apiDescription: `⚠️ ATTENTION: PRERELEASE!
227
- This API is organized into modules based on logical domains.`,
228
-
229
- /******************************************************************************
230
- * REQUEST LIMITS
231
- ******************************************************************************/
232
-
233
- /**
234
- * Default page sizes for list endpoints. Backend enforces max 1000.
235
- * Must include 'default' key as fallback.
236
- */
237
- requestLimits: {
238
- default: 40,
239
- users: 100,
240
- members: 40,
241
- organizations: 40,
242
- requests: 40,
243
- attachments: 40,
244
- pages: 100,
245
- pendingMemberships: 20,
246
- },
247
-
248
- /** Max JSON body size in bytes */
249
- jsonBodyLimit: 1 * 1024 * 1024,
250
- /** Max file upload size in bytes */
251
- fileUploadLimit: 20 * 1024 * 1024,
252
- /** Default body size limit in bytes */
253
- defaultBodyLimit: 1 * 1024 * 1024,
254
-
255
- /******************************************************************************
256
- * STORAGE & UPLOADS (S3)
257
- ******************************************************************************/
258
-
259
- /** S3-compatible storage configuration. Only region and host are required; the rest is derived from the slug. */
260
- s3: {
261
- region: '',
262
- host: '',
263
- } as S3ConfigInput,
264
-
265
- /** Upload template IDs for Transloadit processing pipelines */
266
- uploadTemplateIds: ['avatar', 'cover', 'attachment'] as const,
267
-
268
- /** Uppy upload widget default restrictions */
269
- uppy: {
270
- defaultRestrictions: {
271
- maxFileSize: 10 * 1024 * 1024,
272
- maxNumberOfFiles: 1,
273
- allowedFileTypes: ['.jpg', '.jpeg', '.png'],
274
- maxTotalFileSize: 100 * 1024 * 1024,
275
- minFileSize: null,
276
- minNumberOfFiles: null,
277
- requiredMetaFields: [],
278
- },
279
- },
280
-
281
- /**
282
- * Local blob storage restrictions (IndexedDB/Dexie).
283
- * Controls which attachments are cached locally for offline access.
284
- */
285
- localBlobStorage: {
286
- enabled: true,
287
- maxFileSize: 10 * 1024 * 1024,
288
- maxTotalSize: 100 * 1024 * 1024,
289
- allowedContentTypes: [] as string[],
290
- excludedContentTypes: ['video/*'] as string[],
291
- downloadConcurrency: 2,
292
- uploadRetryAttempts: 3,
293
- uploadRetryDelays: [60000, 300000, 900000] as const,
294
- },
295
-
296
- /******************************************************************************
297
- * THIRD-PARTY SERVICES
298
- ******************************************************************************/
299
-
300
- /** Gleap token for customer support widget */
301
- gleapToken: '',
302
- /** Google Maps API key */
303
- googleMapsKey: '',
304
- /** Matrix homeserver URL for chat integration */
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: '',
308
-
309
- /******************************************************************************
310
- * THEMING & UI
311
- ******************************************************************************/
312
-
313
- /** Primary theme color for PWA manifest and browser chrome */
314
- themeColor: '#26262b',
315
- /** Theme configuration for UI components */
316
- theme: {
317
- navigation: {
318
- hasSidebarTextLabels: false,
319
- sidebarWidthExpanded: '16rem',
320
- sidebarWidthCollapsed: '4rem',
321
- sheetPanelWidth: '20rem',
322
- },
323
- colors: {},
324
- strokeWidth: 1.5,
325
- screenSizes: {
326
- xs: '420px',
327
- sm: '640px',
328
- md: '768px',
329
- lg: '1024px',
330
- xl: '1280px',
331
- '2xl': '1400px',
332
- },
333
- } as const,
334
- /** Placeholder background colors for avatars without images */
335
- placeholderColors: [
336
- 'bg-blue-300',
337
- 'bg-lime-300',
338
- 'bg-orange-300',
339
- 'bg-yellow-300',
340
- 'bg-green-300',
341
- 'bg-teal-300',
342
- 'bg-indigo-300',
343
- 'bg-purple-300',
344
- 'bg-pink-300',
345
- 'bg-red-300',
346
- ],
347
-
348
- /******************************************************************************
349
- * LOCALIZATION
350
- ******************************************************************************/
351
-
352
- /** Default language code */
353
- defaultLanguage: 'en' as const,
354
- /** Available language codes - first is fallback */
355
- languages: ['en', 'nl'] as const,
356
- /** Common reference data */
357
- c: {
358
- countries: ['fr', 'de', 'nl', 'ua', 'us', 'gb'],
359
- timezones: [],
360
- },
361
-
362
- /******************************************************************************
363
- * COMPANY DETAILS
364
- ******************************************************************************/
365
-
366
- /** Company/organization details for footer, legal pages, and contact info */
367
- company: {
368
- name: '__project_name__',
369
- shortName: '__project_name__',
370
- email: 'info@__project_slug__.example.com',
371
- supportEmail: 'support@__project_slug__.example.com',
372
- tel: '',
373
- streetAddress: '',
374
- postcode: '',
375
- city: '',
376
- country: '',
377
- registration: '',
378
- bankAccount: '',
379
- googleMapsUrl: '',
380
- scheduleCallUrl: '',
381
- socialUrl: '',
382
- blueskyHandle: '',
383
- element: '',
384
- githubUrl: '',
385
- mapZoom: 4,
386
- coordinates: {
387
- lat: 0,
388
- lng: 0,
389
- },
390
- },
391
-
392
- /******************************************************************************
393
- * USER DEFAULTS
394
- ******************************************************************************/
395
-
396
- /** Default user flags applied to new users */
397
- defaultUserFlags: {
398
- finishedOnboarding: false,
399
- },
400
- } satisfies RequiredConfig;
401
-
402
- export default config;