vela 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -7,7 +7,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "vela",
10
- version: "0.9.1",
10
+ version: "0.10.1",
11
11
  type: "module",
12
12
  description: "A CLI for creating and updating SvelteKit projects",
13
13
  license: "MIT",
@@ -41,8 +41,8 @@ var package_default = {
41
41
  dependencies: {
42
42
  "@clack/prompts": "^1.7.0",
43
43
  "@faker-js/faker": "^10.6.0",
44
- "@velastack/patterns": "^0.0.61",
45
- "@velastack/pocketbase": "^0.1.0",
44
+ "@velastack/patterns": "^0.1.0",
45
+ "@velastack/pocketbase-codegen": "^0.1.0",
46
46
  "annotate-json-schema": "^0.1.0",
47
47
  commander: "^13.1.0",
48
48
  "cross-spawn": "^7.0.6",
@@ -279,6 +279,161 @@ function stubCommand(name, description, fullName) {
279
279
  return cmd;
280
280
  }
281
281
 
282
+ // src/lib/workspace.ts
283
+ import fs3 from "node:fs";
284
+ import path2 from "node:path";
285
+ import process3 from "node:process";
286
+
287
+ // src/lib/constants.ts
288
+ var DATA_DIR = "data";
289
+ var MIGRATIONS_DIR = "migrations";
290
+ var PUBLIC_DIR = "(public)";
291
+ var APP_DIR = "(app)";
292
+ var LEGAL_DIR = "(legal)";
293
+ var API_URL = "https://velastack.dev";
294
+ var FIXTURE_PREFIX = "vela";
295
+
296
+ // src/lib/package-json.ts
297
+ import fs2 from "node:fs";
298
+ var DEP_KINDS = ["dependencies", "devDependencies"];
299
+ function mergePackageJson(user, template) {
300
+ const merged = structuredClone(user);
301
+ const added = [];
302
+ const conflicts = [];
303
+ const replaced = [];
304
+ for (const kind of DEP_KINDS) {
305
+ const templateDeps = template[kind];
306
+ if (!templateDeps) continue;
307
+ const userDeps = merged[kind] ??= {};
308
+ const otherKind = kind === "dependencies" ? "devDependencies" : "dependencies";
309
+ const userOther = user[otherKind] ?? {};
310
+ for (const [name, templateValue] of Object.entries(templateDeps)) {
311
+ if (name in userDeps) {
312
+ if (userDeps[name] !== templateValue) {
313
+ conflicts.push({ kind, name, templateValue, userValue: userDeps[name] });
314
+ }
315
+ continue;
316
+ }
317
+ if (name in userOther) {
318
+ continue;
319
+ }
320
+ userDeps[name] = templateValue;
321
+ added.push({ kind, name, templateValue });
322
+ }
323
+ }
324
+ const templateScripts = template.scripts;
325
+ if (templateScripts) {
326
+ const userScripts = merged.scripts ??= {};
327
+ for (const [name, templateValue] of Object.entries(templateScripts)) {
328
+ const existing = userScripts[name];
329
+ if (existing === void 0) {
330
+ userScripts[name] = templateValue;
331
+ added.push({ kind: "scripts", name, templateValue });
332
+ continue;
333
+ }
334
+ if (existing === templateValue) continue;
335
+ userScripts[name] = templateValue;
336
+ replaced.push({ kind: "scripts", name, templateValue, userValue: existing });
337
+ }
338
+ }
339
+ for (const kind of DEP_KINDS) {
340
+ const deps = merged[kind];
341
+ if (deps) merged[kind] = sortKeys(deps);
342
+ }
343
+ return { merged, added, conflicts, replaced };
344
+ }
345
+ function readPackageJson(path36) {
346
+ return JSON.parse(fs2.readFileSync(path36, "utf8"));
347
+ }
348
+ var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
349
+ var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
350
+ var CLI_VERSION_PLACEHOLDER = /~VELA_VERSION~/g;
351
+ function fillTemplatePlaceholders(raw, values) {
352
+ const packageName = toValidPackageName(values.appName);
353
+ const appName = escapeSingleQuoted(values.appName);
354
+ return raw.replace(PACKAGE_NAME_PLACEHOLDER, () => packageName).replace(APP_NAME_PLACEHOLDER, () => appName).replace(CLI_VERSION_PLACEHOLDER, () => values.cliVersion);
355
+ }
356
+ function escapeSingleQuoted(value) {
357
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
358
+ }
359
+ function readTemplatePackageJson(path36, values) {
360
+ const raw = fillTemplatePlaceholders(fs2.readFileSync(path36, "utf8"), values);
361
+ return JSON.parse(raw);
362
+ }
363
+ function writePackageJson(path36, pkg) {
364
+ fs2.writeFileSync(path36, JSON.stringify(pkg, null, " ") + "\n");
365
+ }
366
+ function toValidPackageName(name) {
367
+ return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
368
+ }
369
+ function sortKeys(obj) {
370
+ const sorted = {};
371
+ for (const key of Object.keys(obj).sort()) sorted[key] = obj[key];
372
+ return sorted;
373
+ }
374
+
375
+ // src/lib/workspace.ts
376
+ function findWorkspaceRoot(from = process3.cwd()) {
377
+ let currentDir = from;
378
+ while (currentDir !== path2.parse(currentDir).root) {
379
+ if (fs3.existsSync(path2.join(currentDir, "package.json"))) return currentDir;
380
+ currentDir = path2.dirname(currentDir);
381
+ }
382
+ return null;
383
+ }
384
+ function hasBackend(from = process3.cwd()) {
385
+ const root = findWorkspaceRoot(from);
386
+ return root !== null && fs3.existsSync(path2.join(root, DATA_DIR));
387
+ }
388
+ async function getWorkspace() {
389
+ const workspaceRootDir = findWorkspaceRoot();
390
+ if (!workspaceRootDir) {
391
+ throw new Error("Could not find workspace root (no package.json found)");
392
+ }
393
+ const routesDir = path2.join("src", "routes");
394
+ const fullRoutesPath = path2.join(workspaceRootDir, routesDir);
395
+ if (!fs3.existsSync(fullRoutesPath)) {
396
+ throw new Error("Could not find src/routes directory");
397
+ }
398
+ let publicRoutesDir = path2.join(routesDir, PUBLIC_DIR);
399
+ if (!fs3.existsSync(path2.join(workspaceRootDir, publicRoutesDir))) {
400
+ publicRoutesDir = routesDir;
401
+ }
402
+ let appRoutesDir;
403
+ const appRoutesPath = path2.join(fullRoutesPath, APP_DIR);
404
+ const isAppMode = fs3.existsSync(appRoutesPath);
405
+ if (isAppMode) appRoutesDir = path2.join(routesDir, APP_DIR);
406
+ const isPaymentsMode = fs3.existsSync(
407
+ path2.join(workspaceRootDir, routesDir, "webhooks", "stripe")
408
+ );
409
+ const features = detectFeatures(workspaceRootDir, { isAppMode, isPaymentsMode });
410
+ return {
411
+ workspaceRootDir,
412
+ routesDir,
413
+ publicRoutesDir,
414
+ appRoutesDir,
415
+ isAppMode,
416
+ isPaymentsMode,
417
+ features
418
+ };
419
+ }
420
+ function detectFeatures(root, { isAppMode, isPaymentsMode }) {
421
+ const has = (rel) => fs3.existsSync(path2.join(root, rel));
422
+ const pkg = readPackageJson(path2.join(root, "package.json"));
423
+ const hasDep = (name) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
424
+ return {
425
+ auth: isAppMode,
426
+ api: has("src/routes/api"),
427
+ apiKeys: has("src/routes/api/api-keys") || has("src/routes/(app)/api-keys"),
428
+ backend: has(DATA_DIR),
429
+ i18n: has("src/lib/i18n") || has("messages"),
430
+ teams: has("src/routes/(app)/teams") || has("src/lib/teams"),
431
+ payments: isPaymentsMode,
432
+ blog: hasDep("mdsvex"),
433
+ contentNegotiation: hasDep("sveltekit-negotiate")
434
+ };
435
+ }
436
+
282
437
  // src/commands/bless.ts
283
438
  import fs12 from "node:fs";
284
439
  import path11 from "node:path";
@@ -317,32 +472,32 @@ function toFlag(key) {
317
472
  }
318
473
 
319
474
  // src/lib/templates.ts
320
- import fs2 from "node:fs";
321
- import path2 from "node:path";
475
+ import fs4 from "node:fs";
476
+ import path3 from "node:path";
322
477
  import { fileURLToPath } from "node:url";
323
478
  var TEMPLATE_MANIFEST = "template.json";
324
479
  var DEFAULT_TEMPLATE = "minimal";
325
480
  var cached;
326
481
  function templatesDir() {
327
- let dir = path2.dirname(fileURLToPath(import.meta.url));
328
- const { root } = path2.parse(dir);
482
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
483
+ const { root } = path3.parse(dir);
329
484
  while (dir !== root) {
330
- const candidate = path2.join(dir, "templates");
485
+ const candidate = path3.join(dir, "templates");
331
486
  if (holdsManifest(candidate)) return candidate;
332
- dir = path2.dirname(dir);
487
+ dir = path3.dirname(dir);
333
488
  }
334
489
  throw new Error("Could not locate the templates directory");
335
490
  }
336
491
  function holdsManifest(dir) {
337
- if (!fs2.existsSync(dir)) return false;
338
- return fs2.readdirSync(dir, { withFileTypes: true }).some(
339
- (entry) => entry.isDirectory() && fs2.existsSync(path2.join(dir, entry.name, TEMPLATE_MANIFEST))
492
+ if (!fs4.existsSync(dir)) return false;
493
+ return fs4.readdirSync(dir, { withFileTypes: true }).some(
494
+ (entry) => entry.isDirectory() && fs4.existsSync(path3.join(dir, entry.name, TEMPLATE_MANIFEST))
340
495
  );
341
496
  }
342
497
  function listProjectTemplates() {
343
498
  if (cached) return cached;
344
499
  const root = templatesDir();
345
- cached = fs2.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readManifest(root, entry.name)).filter((template) => template !== void 0).sort((a, b) => a.name.localeCompare(b.name));
500
+ cached = fs4.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readManifest(root, entry.name)).filter((template) => template !== void 0).sort((a, b) => a.name.localeCompare(b.name));
346
501
  return cached;
347
502
  }
348
503
  function projectTemplateNames(options = {}) {
@@ -356,11 +511,11 @@ function findProjectTemplate(name) {
356
511
  return template;
357
512
  }
358
513
  function readManifest(root, name) {
359
- const manifestPath = path2.join(root, name, TEMPLATE_MANIFEST);
360
- if (!fs2.existsSync(manifestPath)) return void 0;
514
+ const manifestPath = path3.join(root, name, TEMPLATE_MANIFEST);
515
+ if (!fs4.existsSync(manifestPath)) return void 0;
361
516
  let parsed;
362
517
  try {
363
- parsed = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
518
+ parsed = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
364
519
  } catch (e) {
365
520
  throw new Error(
366
521
  `Template ${name} has an unreadable ${TEMPLATE_MANIFEST}: ${e.message}`
@@ -376,14 +531,14 @@ function readManifest(root, name) {
376
531
  name,
377
532
  description: manifest.description,
378
533
  backend: manifest.backend,
379
- dir: path2.join(root, name)
534
+ dir: path3.join(root, name)
380
535
  };
381
536
  }
382
537
 
383
538
  // src/lib/package-manager.ts
384
- import fs3 from "node:fs";
385
- import path3 from "node:path";
386
- import process3 from "node:process";
539
+ import fs5 from "node:fs";
540
+ import path4 from "node:path";
541
+ import process4 from "node:process";
387
542
  import { exec } from "tinyexec";
388
543
  import { Option } from "commander";
389
544
  import * as p2 from "@clack/prompts";
@@ -399,7 +554,7 @@ var installOption = new Option(
399
554
  "installs dependencies with a specified package manager"
400
555
  ).choices(AGENT_NAMES);
401
556
  function getUserAgent() {
402
- const userAgent = process3.env.npm_config_user_agent;
557
+ const userAgent = process4.env.npm_config_user_agent;
403
558
  if (!userAgent) return void 0;
404
559
  const pmSpec = userAgent.split(" ")[0];
405
560
  const separatorPos = pmSpec.lastIndexOf("/");
@@ -409,7 +564,7 @@ function getUserAgent() {
409
564
  async function packageManagerPrompt(cwd) {
410
565
  const detected = await detect({ cwd });
411
566
  const agent = detected?.name ?? getUserAgent();
412
- if (!process3.stdout.isTTY) return agent;
567
+ if (!process4.stdout.isTTY) return agent;
413
568
  const options = [
414
569
  { label: "None", value: void 0 },
415
570
  ...AGENT_NAMES.map((pm2) => ({ value: pm2, label: pm2 }))
@@ -421,14 +576,14 @@ async function packageManagerPrompt(cwd) {
421
576
  });
422
577
  if (p2.isCancel(pm)) {
423
578
  p2.cancel("Operation cancelled.");
424
- process3.exit(1);
579
+ process4.exit(1);
425
580
  }
426
581
  return pm;
427
582
  }
428
583
  async function installDependencies(agent, cwd) {
429
584
  const task = p2.taskLog({
430
585
  title: `Installing dependencies with ${agent}...`,
431
- limit: Math.ceil(process3.stdout.rows / 2),
586
+ limit: Math.ceil(process4.stdout.rows / 2),
432
587
  spacing: 0,
433
588
  retainLog: true
434
589
  });
@@ -445,14 +600,14 @@ async function installDependencies(agent, cwd) {
445
600
  } catch {
446
601
  task.error("Failed to install dependencies");
447
602
  p2.cancel("Operation failed.");
448
- process3.exit(2);
603
+ process4.exit(2);
449
604
  }
450
605
  }
451
606
  function addPnpmBuildDependencies(cwd, packageManager, allowedPackages) {
452
607
  if (!packageManager || packageManager !== "pnpm") return;
453
- const pkgPath = path3.join(cwd, "package.json");
454
- if (!fs3.existsSync(pkgPath)) return;
455
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
608
+ const pkgPath = path4.join(cwd, "package.json");
609
+ if (!fs5.existsSync(pkgPath)) return;
610
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
456
611
  pkg.pnpm ??= {};
457
612
  pkg.pnpm.onlyBuiltDependencies ??= [];
458
613
  for (const name of allowedPackages) {
@@ -460,30 +615,19 @@ function addPnpmBuildDependencies(cwd, packageManager, allowedPackages) {
460
615
  pkg.pnpm.onlyBuiltDependencies.push(name);
461
616
  }
462
617
  }
463
- fs3.writeFileSync(pkgPath, JSON.stringify(pkg, null, " ") + "\n");
618
+ fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, " ") + "\n");
464
619
  }
465
620
 
466
621
  // src/lib/pocketbase.ts
467
- import fs4 from "node:fs";
622
+ import fs6 from "node:fs";
468
623
  import net from "node:net";
469
- import path4 from "node:path";
470
- import process4 from "node:process";
624
+ import path5 from "node:path";
625
+ import process5 from "node:process";
471
626
  import { spawn } from "node:child_process";
472
627
  import PocketBase from "pocketbase";
473
628
  import { detect as detect2 } from "package-manager-detector";
474
629
  import { resolveCommand } from "package-manager-detector/commands";
475
630
  import { x } from "tinyexec";
476
-
477
- // src/lib/constants.ts
478
- var DATA_DIR = "data";
479
- var MIGRATIONS_DIR = "migrations";
480
- var PUBLIC_DIR = "(public)";
481
- var APP_DIR = "(app)";
482
- var LEGAL_DIR = "(legal)";
483
- var API_URL = "https://velastack.dev";
484
- var FIXTURE_PREFIX = "vela";
485
-
486
- // src/lib/pocketbase.ts
487
631
  function findFreePort(host = "localhost") {
488
632
  return new Promise((resolve, reject) => {
489
633
  const server = net.createServer();
@@ -589,9 +733,9 @@ async function authWithRetries(pb, email3, password8, attempts = 3) {
589
733
  }
590
734
  }
591
735
  function getPocketbaseMetadata(cwd) {
592
- const metadataPath = path4.join(cwd, "node_modules", ".vite", "_pocketbase_metadata.json");
593
- if (fs4.existsSync(metadataPath)) {
594
- return JSON.parse(fs4.readFileSync(metadataPath, "utf8"));
736
+ const metadataPath = path5.join(cwd, "node_modules", ".vite", "_pocketbase_metadata.json");
737
+ if (fs6.existsSync(metadataPath)) {
738
+ return JSON.parse(fs6.readFileSync(metadataPath, "utf8"));
595
739
  }
596
740
  return null;
597
741
  }
@@ -604,12 +748,12 @@ async function execPackageBin(cwd, args, stdio = "pipe") {
604
748
  return x(command, resolvedArgs, { nodeOptions: { cwd, stdio }, throwOnError: true });
605
749
  }
606
750
  async function withPocketbase(cwd, fn, creds) {
607
- const dir = path4.join(cwd, DATA_DIR);
608
- const migrationsDir = path4.join(cwd, MIGRATIONS_DIR);
751
+ const dir = path5.join(cwd, DATA_DIR);
752
+ const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
609
753
  const host = "localhost";
610
- const email3 = creds?.email ?? process4.env.POCKETBASE_SUPERUSER_EMAIL;
611
- const password8 = creds?.password ?? process4.env.POCKETBASE_SUPERUSER_PASSWORD;
612
- if (!fs4.existsSync(dir)) {
754
+ const email3 = creds?.email ?? process5.env.POCKETBASE_SUPERUSER_EMAIL;
755
+ const password8 = creds?.password ?? process5.env.POCKETBASE_SUPERUSER_PASSWORD;
756
+ if (!fs6.existsSync(dir)) {
613
757
  throw new Error("PocketBase data directory does not exist");
614
758
  }
615
759
  const metadata = getPocketbaseMetadata(cwd);
@@ -634,10 +778,10 @@ async function withPocketbase(cwd, fn, creds) {
634
778
  }
635
779
  }
636
780
  async function createSuperuser(cwd, email3, password8) {
637
- const dir = path4.join(cwd, DATA_DIR);
638
- const migrationsDir = path4.join(cwd, MIGRATIONS_DIR);
639
- fs4.mkdirSync(dir, { recursive: true });
640
- fs4.mkdirSync(migrationsDir, { recursive: true });
781
+ const dir = path5.join(cwd, DATA_DIR);
782
+ const migrationsDir = path5.join(cwd, MIGRATIONS_DIR);
783
+ fs6.mkdirSync(dir, { recursive: true });
784
+ fs6.mkdirSync(migrationsDir, { recursive: true });
641
785
  await execPackageBin(
642
786
  cwd,
643
787
  [
@@ -661,8 +805,8 @@ async function launchPocketbase(cwd, {
661
805
  password: password8
662
806
  }) {
663
807
  const host = "localhost";
664
- fs4.mkdirSync(dir, { recursive: true });
665
- fs4.mkdirSync(migrationsDir, { recursive: true });
808
+ fs6.mkdirSync(dir, { recursive: true });
809
+ fs6.mkdirSync(migrationsDir, { recursive: true });
666
810
  await execPackageBin(
667
811
  cwd,
668
812
  [
@@ -696,8 +840,8 @@ async function launchPocketbase(cwd, {
696
840
  }
697
841
 
698
842
  // src/lib/env.ts
699
- import fs5 from "node:fs";
700
- import path5 from "node:path";
843
+ import fs7 from "node:fs";
844
+ import path6 from "node:path";
701
845
  function addEnvVar(content, key, value) {
702
846
  if (content.includes(`${key}=`)) return content;
703
847
  return appendLine(content, `${key}=${value}`);
@@ -712,99 +856,20 @@ function appendLine(existing, line) {
712
856
  return withNewline + line + "\n";
713
857
  }
714
858
  function writeEnvFile(cwd, vars, comments = []) {
715
- const envPath = path5.join(cwd, ".env");
716
- let content = fs5.existsSync(envPath) ? fs5.readFileSync(envPath, "utf8") : "";
859
+ const envPath = path6.join(cwd, ".env");
860
+ let content = fs7.existsSync(envPath) ? fs7.readFileSync(envPath, "utf8") : "";
717
861
  for (const comment of comments) content = addEnvComment(content, comment);
718
862
  for (const [key, value] of Object.entries(vars)) content = addEnvVar(content, key, value);
719
- fs5.writeFileSync(envPath, content);
720
- }
721
-
722
- // src/lib/package-json.ts
723
- import fs6 from "node:fs";
724
- var DEP_KINDS = ["dependencies", "devDependencies"];
725
- function mergePackageJson(user, template) {
726
- const merged = structuredClone(user);
727
- const added = [];
728
- const conflicts = [];
729
- const replaced = [];
730
- for (const kind of DEP_KINDS) {
731
- const templateDeps = template[kind];
732
- if (!templateDeps) continue;
733
- const userDeps = merged[kind] ??= {};
734
- const otherKind = kind === "dependencies" ? "devDependencies" : "dependencies";
735
- const userOther = user[otherKind] ?? {};
736
- for (const [name, templateValue] of Object.entries(templateDeps)) {
737
- if (name in userDeps) {
738
- if (userDeps[name] !== templateValue) {
739
- conflicts.push({ kind, name, templateValue, userValue: userDeps[name] });
740
- }
741
- continue;
742
- }
743
- if (name in userOther) {
744
- continue;
745
- }
746
- userDeps[name] = templateValue;
747
- added.push({ kind, name, templateValue });
748
- }
749
- }
750
- const templateScripts = template.scripts;
751
- if (templateScripts) {
752
- const userScripts = merged.scripts ??= {};
753
- for (const [name, templateValue] of Object.entries(templateScripts)) {
754
- const existing = userScripts[name];
755
- if (existing === void 0) {
756
- userScripts[name] = templateValue;
757
- added.push({ kind: "scripts", name, templateValue });
758
- continue;
759
- }
760
- if (existing === templateValue) continue;
761
- userScripts[name] = templateValue;
762
- replaced.push({ kind: "scripts", name, templateValue, userValue: existing });
763
- }
764
- }
765
- for (const kind of DEP_KINDS) {
766
- const deps = merged[kind];
767
- if (deps) merged[kind] = sortKeys(deps);
768
- }
769
- return { merged, added, conflicts, replaced };
770
- }
771
- function readPackageJson(path36) {
772
- return JSON.parse(fs6.readFileSync(path36, "utf8"));
773
- }
774
- var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
775
- var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
776
- var CLI_VERSION_PLACEHOLDER = /~VELA_VERSION~/g;
777
- function fillTemplatePlaceholders(raw, values) {
778
- const packageName = toValidPackageName(values.appName);
779
- const appName = escapeSingleQuoted(values.appName);
780
- return raw.replace(PACKAGE_NAME_PLACEHOLDER, () => packageName).replace(APP_NAME_PLACEHOLDER, () => appName).replace(CLI_VERSION_PLACEHOLDER, () => values.cliVersion);
781
- }
782
- function escapeSingleQuoted(value) {
783
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
784
- }
785
- function readTemplatePackageJson(path36, values) {
786
- const raw = fillTemplatePlaceholders(fs6.readFileSync(path36, "utf8"), values);
787
- return JSON.parse(raw);
788
- }
789
- function writePackageJson(path36, pkg) {
790
- fs6.writeFileSync(path36, JSON.stringify(pkg, null, " ") + "\n");
791
- }
792
- function toValidPackageName(name) {
793
- return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
794
- }
795
- function sortKeys(obj) {
796
- const sorted = {};
797
- for (const key of Object.keys(obj).sort()) sorted[key] = obj[key];
798
- return sorted;
863
+ fs7.writeFileSync(envPath, content);
799
864
  }
800
865
 
801
866
  // src/lib/config-merge.ts
802
- import fs8 from "node:fs";
803
- import path7 from "node:path";
867
+ import fs9 from "node:fs";
868
+ import path8 from "node:path";
804
869
 
805
870
  // src/lib/config-target.ts
806
- import fs7 from "node:fs";
807
- import path6 from "node:path";
871
+ import fs8 from "node:fs";
872
+ import path7 from "node:path";
808
873
  import {
809
874
  Project,
810
875
  QuoteKind,
@@ -824,8 +889,8 @@ var SVELTE_CONFIG_CANDIDATES = [
824
889
  ];
825
890
  function probeFirstExisting(root, candidates) {
826
891
  for (const rel of candidates) {
827
- const abs = path6.join(root, rel);
828
- if (fs7.existsSync(abs)) return abs;
892
+ const abs = path7.join(root, rel);
893
+ if (fs8.existsSync(abs)) return abs;
829
894
  }
830
895
  return null;
831
896
  }
@@ -901,7 +966,7 @@ function mergeSvelteConfig(projectRoot) {
901
966
  };
902
967
  }
903
968
  function mergeRunesIntoViteArg(vite, arg) {
904
- const file = path7.basename(vite.filePath);
969
+ const file = path8.basename(vite.filePath);
905
970
  const compilerOptions = getOrCreateObjectLiteralProperty(arg, "compilerOptions", "{}");
906
971
  if (!compilerOptions) {
907
972
  return {
@@ -920,8 +985,8 @@ function mergeRunesIntoViteArg(vite, arg) {
920
985
  return { applied: true, reason: "added runes compilerOption", file };
921
986
  }
922
987
  function mergeRunesIntoSvelteConfig(filePath) {
923
- const file = path7.basename(filePath);
924
- const original = fs8.readFileSync(filePath, "utf8");
988
+ const file = path8.basename(filePath);
989
+ const original = fs9.readFileSync(filePath, "utf8");
925
990
  if (/runes\s*:/m.test(original)) {
926
991
  return { applied: false, reason: "runes already configured", file };
927
992
  }
@@ -944,11 +1009,11 @@ function mergeRunesIntoSvelteConfig(filePath) {
944
1009
  }
945
1010
  const insertAt = anchor.index + anchor[0].length;
946
1011
  const updated = original.slice(0, insertAt) + RUNES_SNIPPET + original.slice(insertAt);
947
- fs8.writeFileSync(filePath, updated);
1012
+ fs9.writeFileSync(filePath, updated);
948
1013
  return { applied: true, reason: "added runes compilerOption", file };
949
1014
  }
950
1015
  function mergeViteConfig(filePath) {
951
- if (!fs8.existsSync(filePath)) {
1016
+ if (!fs9.existsSync(filePath)) {
952
1017
  return {
953
1018
  applied: false,
954
1019
  reason: "vite.config.ts not found",
@@ -956,7 +1021,7 @@ function mergeViteConfig(filePath) {
956
1021
  // then add tailwindcss() to the plugins array`
957
1022
  };
958
1023
  }
959
- const original = fs8.readFileSync(filePath, "utf8");
1024
+ const original = fs9.readFileSync(filePath, "utf8");
960
1025
  if (original.includes("@tailwindcss/vite")) {
961
1026
  return { applied: false, reason: "tailwindcss plugin already present" };
962
1027
  }
@@ -975,14 +1040,14 @@ function mergeViteConfig(filePath) {
975
1040
  const trailing = withImport.slice(insertAt);
976
1041
  const prefix = /^\s*\]/.test(trailing) ? "tailwindcss()" : "tailwindcss(), ";
977
1042
  const updated = withImport.slice(0, insertAt) + prefix + withImport.slice(insertAt);
978
- fs8.writeFileSync(filePath, updated);
1043
+ fs9.writeFileSync(filePath, updated);
979
1044
  return { applied: true, reason: "added @tailwindcss/vite plugin" };
980
1045
  }
981
1046
  function mergeTsconfig(filePath) {
982
- if (!fs8.existsSync(filePath)) {
1047
+ if (!fs9.existsSync(filePath)) {
983
1048
  return { applied: false, reason: "tsconfig.json not found" };
984
1049
  }
985
- const original = fs8.readFileSync(filePath, "utf8");
1050
+ const original = fs9.readFileSync(filePath, "utf8");
986
1051
  if (/rewriteRelativeImportExtensions/.test(original)) {
987
1052
  return { applied: false, reason: "rewriteRelativeImportExtensions already set" };
988
1053
  }
@@ -998,7 +1063,7 @@ function mergeTsconfig(filePath) {
998
1063
  const indent = detectIndent(original, insertAt);
999
1064
  const updated = original.slice(0, insertAt) + `
1000
1065
  ${indent}"rewriteRelativeImportExtensions": true,` + original.slice(insertAt);
1001
- fs8.writeFileSync(filePath, updated);
1066
+ fs9.writeFileSync(filePath, updated);
1002
1067
  return { applied: true, reason: "added rewriteRelativeImportExtensions" };
1003
1068
  }
1004
1069
  var GITIGNORE_ENTRIES = [
@@ -1010,7 +1075,7 @@ var GITIGNORE_ENTRIES = [
1010
1075
  "vite.config.ts.timestamp-*"
1011
1076
  ];
1012
1077
  function mergeGitignore(filePath) {
1013
- const existing = fs8.existsSync(filePath) ? fs8.readFileSync(filePath, "utf8") : "";
1078
+ const existing = fs9.existsSync(filePath) ? fs9.readFileSync(filePath, "utf8") : "";
1014
1079
  const lines = existing.split("\n").map((l) => l.trim());
1015
1080
  const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.includes(entry));
1016
1081
  if (missing.length === 0) {
@@ -1019,7 +1084,7 @@ function mergeGitignore(filePath) {
1019
1084
  const needsNewline = existing.length > 0 && !existing.endsWith("\n");
1020
1085
  const appended = `${existing}${needsNewline ? "\n" : ""}${missing.join("\n")}
1021
1086
  `;
1022
- fs8.writeFileSync(filePath, appended);
1087
+ fs9.writeFileSync(filePath, appended);
1023
1088
  return { applied: true, reason: `added ${missing.length} gitignore entries` };
1024
1089
  }
1025
1090
  function addImport(source, importLine) {
@@ -1044,75 +1109,14 @@ function detectIndent(source, atOffset) {
1044
1109
  }
1045
1110
 
1046
1111
  // src/lib/scaffold-detect.ts
1047
- import fs9 from "node:fs";
1048
- import path8 from "node:path";
1049
- var VANILLA_MARKER = "Welcome to SvelteKit";
1050
- var PAGE_REL = path8.join("src", "routes", "+page.svelte");
1051
- function isVanillaRoutes(cwd) {
1052
- const pagePath = path8.join(cwd, PAGE_REL);
1053
- if (!fs9.existsSync(pagePath)) return false;
1054
- return fs9.readFileSync(pagePath, "utf8").includes(VANILLA_MARKER);
1055
- }
1056
-
1057
- // src/lib/workspace.ts
1058
1112
  import fs10 from "node:fs";
1059
1113
  import path9 from "node:path";
1060
- import process5 from "node:process";
1061
- async function getWorkspace() {
1062
- let currentDir = process5.cwd();
1063
- let workspaceRootDir = "";
1064
- while (currentDir !== path9.parse(currentDir).root) {
1065
- if (fs10.existsSync(path9.join(currentDir, "package.json"))) {
1066
- workspaceRootDir = currentDir;
1067
- break;
1068
- }
1069
- currentDir = path9.dirname(currentDir);
1070
- }
1071
- if (!workspaceRootDir) {
1072
- throw new Error("Could not find workspace root (no package.json found)");
1073
- }
1074
- const routesDir = path9.join("src", "routes");
1075
- const fullRoutesPath = path9.join(workspaceRootDir, routesDir);
1076
- if (!fs10.existsSync(fullRoutesPath)) {
1077
- throw new Error("Could not find src/routes directory");
1078
- }
1079
- let publicRoutesDir = path9.join(routesDir, PUBLIC_DIR);
1080
- if (!fs10.existsSync(path9.join(workspaceRootDir, publicRoutesDir))) {
1081
- publicRoutesDir = routesDir;
1082
- }
1083
- let appRoutesDir;
1084
- const appRoutesPath = path9.join(fullRoutesPath, APP_DIR);
1085
- const isAppMode = fs10.existsSync(appRoutesPath);
1086
- if (isAppMode) appRoutesDir = path9.join(routesDir, APP_DIR);
1087
- const isPaymentsMode = fs10.existsSync(
1088
- path9.join(workspaceRootDir, routesDir, "webhooks", "stripe")
1089
- );
1090
- const features = detectFeatures(workspaceRootDir, { isAppMode, isPaymentsMode });
1091
- return {
1092
- workspaceRootDir,
1093
- routesDir,
1094
- publicRoutesDir,
1095
- appRoutesDir,
1096
- isAppMode,
1097
- isPaymentsMode,
1098
- features
1099
- };
1100
- }
1101
- function detectFeatures(root, { isAppMode, isPaymentsMode }) {
1102
- const has = (rel) => fs10.existsSync(path9.join(root, rel));
1103
- const pkg = readPackageJson(path9.join(root, "package.json"));
1104
- const hasDep = (name) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
1105
- return {
1106
- auth: isAppMode,
1107
- api: has("src/routes/api"),
1108
- apiKeys: has("src/routes/api/api-keys") || has("src/routes/(app)/api-keys"),
1109
- backend: has(DATA_DIR),
1110
- i18n: has("src/lib/i18n") || has("messages"),
1111
- teams: has("src/routes/(app)/teams") || has("src/lib/teams"),
1112
- payments: isPaymentsMode,
1113
- blog: hasDep("mdsvex"),
1114
- contentNegotiation: hasDep("sveltekit-negotiate")
1115
- };
1114
+ var VANILLA_MARKER = "Welcome to SvelteKit";
1115
+ var PAGE_REL = path9.join("src", "routes", "+page.svelte");
1116
+ function isVanillaRoutes(cwd) {
1117
+ const pagePath = path9.join(cwd, PAGE_REL);
1118
+ if (!fs10.existsSync(pagePath)) return false;
1119
+ return fs10.readFileSync(pagePath, "utf8").includes(VANILLA_MARKER);
1116
1120
  }
1117
1121
 
1118
1122
  // src/lib/result-report.ts
@@ -1692,6 +1696,17 @@ async function runPattern(slug, argv, input, report) {
1692
1696
  root: workspaceRootDir,
1693
1697
  features,
1694
1698
  input,
1699
+ // Patterns no longer read the schema themselves: @velastack/pocketbase-codegen
1700
+ // takes an injected client, and only the CLI knows how to reach (or spawn)
1701
+ // a PocketBase for this workspace.
1702
+ getCollections: async () => {
1703
+ const { getCollections } = await import("@velastack/pocketbase-codegen");
1704
+ let collections2 = [];
1705
+ await withPocketbase(workspaceRootDir, async (pb) => {
1706
+ collections2 = await getCollections(pb);
1707
+ });
1708
+ return collections2;
1709
+ },
1695
1710
  logger: { info: (message) => log18.message(message) }
1696
1711
  });
1697
1712
  log18.success(report.task.success);
@@ -4892,7 +4907,7 @@ import * as p22 from "@clack/prompts";
4892
4907
  import { annotate } from "annotate-json-schema";
4893
4908
  import { createGenerator } from "json-schema-faker";
4894
4909
  import { faker } from "@faker-js/faker";
4895
- import { collectionToJsonSchema } from "@velastack/pocketbase/internal";
4910
+ import { collectionToJsonSchema } from "@velastack/pocketbase-codegen";
4896
4911
  var SKIP_TYPES = /* @__PURE__ */ new Set(["autodate", "file", "relation"]);
4897
4912
  var AUTH_OVERRIDE_FIELDS = ["email", "password", "passwordConfirm", "tokenKey"];
4898
4913
  function padZeros(num, length) {
@@ -5594,17 +5609,25 @@ import fs27 from "node:fs";
5594
5609
  import path29 from "node:path";
5595
5610
  import process17 from "node:process";
5596
5611
  import { performance } from "node:perf_hooks";
5597
- import { Command as Command65 } from "commander";
5612
+ import { Command as Command65, InvalidArgumentError as InvalidArgumentError5 } from "commander";
5598
5613
  import pc5 from "picocolors";
5599
5614
  import PocketBase2 from "pocketbase";
5600
- var dev = new Command65("dev").description("start the development server").configureHelp(helpConfig).action(async () => {
5615
+ function parsePort(value) {
5616
+ const port = Number(value);
5617
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
5618
+ throw new InvalidArgumentError5("must be a whole number between 1 and 65535.");
5619
+ }
5620
+ return port;
5621
+ }
5622
+ var dev = new Command65("dev").description("start the development server").option("--open", "open the app in a browser once the server is ready").option("--host [host]", "expose the server on the network").option("--port <port>", "port to listen on", parsePort).configureHelp(helpConfig).action(async (options) => {
5601
5623
  const cwd = process17.cwd();
5602
5624
  const startTime = performance.now();
5603
5625
  const { createServer, version } = await import("vite");
5604
5626
  const viteMetadataDir = path29.join(cwd, "node_modules", ".vite");
5605
5627
  const viteMetadataFile = path29.join(viteMetadataDir, "_pocketbase_metadata.json");
5606
5628
  let pbProc;
5607
- const needsStart = !process17.env.POCKETBASE_URL;
5629
+ const backend3 = hasBackend(cwd);
5630
+ const needsStart = backend3 && !process17.env.POCKETBASE_URL;
5608
5631
  const cleanup = () => {
5609
5632
  if (pbProc?.pid) pbProc.kill();
5610
5633
  if (fs27.existsSync(viteMetadataFile)) fs27.rmSync(viteMetadataFile);
@@ -5630,8 +5653,13 @@ var dev = new Command65("dev").description("start the development server").confi
5630
5653
  process17.exit(0);
5631
5654
  });
5632
5655
  }
5633
- const server = await createServer();
5656
+ const serverOptions = {};
5657
+ if (options.open !== void 0) serverOptions.open = options.open;
5658
+ if (options.host !== void 0) serverOptions.host = options.host;
5659
+ if (options.port !== void 0) serverOptions.port = options.port;
5660
+ const server = await createServer({ server: serverOptions });
5634
5661
  server.httpServer?.on("listening", async () => {
5662
+ if (!backend3) return;
5635
5663
  const { address, port: vitePort } = server.httpServer.address();
5636
5664
  const viteHost = address === "::1" ? "localhost" : address;
5637
5665
  await fs27.promises.mkdir(viteMetadataDir, { recursive: true });
@@ -5649,7 +5677,7 @@ var dev = new Command65("dev").description("start the development server").confi
5649
5677
  process17.env.POCKETBASE_SUPERUSER_PASSWORD
5650
5678
  );
5651
5679
  await pb.settings.update({ meta: { appURL: `http://${viteHost}:${vitePort}` } });
5652
- await startWatchingTypes(cwd);
5680
+ await startWatchingTypes(cwd, pb);
5653
5681
  });
5654
5682
  await server.listen();
5655
5683
  const hasExistingLogs = process17.stdout.bytesWritten > 0 || process17.stderr.bytesWritten > 0;
@@ -5665,25 +5693,25 @@ var dev = new Command65("dev").description("start the development server").confi
5665
5693
  server.printUrls();
5666
5694
  server.bindCLIShortcuts({ print: true });
5667
5695
  });
5668
- async function startWatchingTypes(cwd) {
5669
- const { processTypes } = await import("@velastack/pocketbase/internal");
5670
- const config = {
5671
- pocketbaseUrl: process17.env.POCKETBASE_URL,
5672
- superuserEmail: process17.env.POCKETBASE_SUPERUSER_EMAIL,
5673
- superuserPassword: process17.env.POCKETBASE_SUPERUSER_PASSWORD
5674
- };
5696
+ async function startWatchingTypes(cwd, pb) {
5697
+ const { processTypes } = await import("@velastack/pocketbase-codegen");
5675
5698
  const typesDir = path29.resolve(cwd, ".svelte-kit", "types");
5676
5699
  const pocketbaseDir = path29.join(typesDir, "pocketbase");
5677
5700
  const pocketbaseTypes = path29.join(pocketbaseDir, "$types.d.ts");
5678
- await processTypes(config, typesDir);
5679
- const watcher = fs27.promises.watch(pocketbaseDir);
5680
- (async () => {
5681
- for await (const event of watcher) {
5682
- if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs27.existsSync(pocketbaseTypes)) {
5683
- setTimeout(() => {
5684
- processTypes(config, typesDir).catch(() => {
5685
- });
5686
- }, 100);
5701
+ const regenerate = () => processTypes(pb, typesDir).catch(() => {
5702
+ });
5703
+ await regenerate();
5704
+ void (async () => {
5705
+ for (; ; ) {
5706
+ try {
5707
+ await fs27.promises.mkdir(pocketbaseDir, { recursive: true });
5708
+ for await (const event of fs27.promises.watch(pocketbaseDir)) {
5709
+ if (event.eventType === "rename" && event.filename === "$types.d.ts" && !fs27.existsSync(pocketbaseTypes)) {
5710
+ setTimeout(regenerate, 100);
5711
+ }
5712
+ }
5713
+ } catch {
5714
+ await new Promise((r) => setTimeout(r, 200));
5687
5715
  }
5688
5716
  }
5689
5717
  })();
@@ -5700,7 +5728,7 @@ var build = new Command66("build").description("build the app").configureHelp(he
5700
5728
  process18.env.VITE_BUILD = "true";
5701
5729
  const cwd = process18.cwd();
5702
5730
  let pbProc;
5703
- const needsStart = !process18.env.POCKETBASE_URL;
5731
+ const needsStart = hasBackend(cwd) && !process18.env.POCKETBASE_URL;
5704
5732
  const cleanup = () => {
5705
5733
  if (pbProc?.pid) pbProc.kill();
5706
5734
  };
@@ -5744,7 +5772,7 @@ import { resolveCommand as resolveCommand6 } from "package-manager-detector/comm
5744
5772
  var preview = new Command67("preview").description("preview the built app").configureHelp(helpConfig).action(async () => {
5745
5773
  const cwd = process19.cwd();
5746
5774
  let pbProc;
5747
- const needsStart = !process19.env.POCKETBASE_URL;
5775
+ const needsStart = hasBackend(cwd) && !process19.env.POCKETBASE_URL;
5748
5776
  const cleanup = () => {
5749
5777
  if (pbProc?.pid) pbProc.kill();
5750
5778
  };
@@ -5785,15 +5813,10 @@ var sync = new Command68("sync").description("sync types from the database").con
5785
5813
  () => runCommand(async () => {
5786
5814
  const { workspaceRootDir } = await getWorkspace();
5787
5815
  const typesDir = path32.join(workspaceRootDir, ".svelte-kit", "types");
5788
- const { processTypes } = await import("@velastack/pocketbase/internal");
5789
- await processTypes(
5790
- {
5791
- pocketbaseUrl: process.env.POCKETBASE_URL ?? "",
5792
- superuserEmail: process.env.POCKETBASE_SUPERUSER_EMAIL,
5793
- superuserPassword: process.env.POCKETBASE_SUPERUSER_PASSWORD
5794
- },
5795
- typesDir
5796
- );
5816
+ const { processTypes } = await import("@velastack/pocketbase-codegen");
5817
+ await withPocketbase(workspaceRootDir, async (pb) => {
5818
+ await processTypes(pb, typesDir);
5819
+ });
5797
5820
  console.log("types synced");
5798
5821
  }, "Failed to sync types.")
5799
5822
  );
@@ -6171,6 +6194,7 @@ var NO_BACKEND_COMMMANDS = /* @__PURE__ */ new Set([
6171
6194
  "generate schema",
6172
6195
  "generate form"
6173
6196
  ]);
6197
+ var BACKEND_OPTIONAL_COMMANDS = /* @__PURE__ */ new Set(["dev", "build", "preview", "deploy"]);
6174
6198
  var program = new Command73().name(package_default.name).description(package_default.description).version(package_default.version, "-v, --version").configureHelp(helpConfig);
6175
6199
  program.hook("preAction", (_thisCommand, actionCommand) => {
6176
6200
  if (isStub(actionCommand)) return;
@@ -6179,6 +6203,19 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
6179
6203
  if (NO_BACKEND_COMMMANDS.has(path36)) return;
6180
6204
  const top = path36.split(" ", 1)[0];
6181
6205
  if (NO_BACKEND_COMMMANDS.has(top)) return;
6206
+ if (!hasBackend()) {
6207
+ if (BACKEND_OPTIONAL_COMMANDS.has(top)) return;
6208
+ p29.log.error(
6209
+ `${pc7.cyan(`vela ${path36}`)} needs a backend, and this project does not have one.
6210
+
6211
+ Static projects have no database to talk to.
6212
+
6213
+ To add a backend to this project, run ${pc7.cyan("vela bless")}.`
6214
+ );
6215
+ p29.log.message();
6216
+ p29.cancel("Operation failed.");
6217
+ process23.exit(1);
6218
+ }
6182
6219
  if (!process23.env.POCKETBASE_SUPERUSER_EMAIL || !process23.env.POCKETBASE_SUPERUSER_PASSWORD) {
6183
6220
  p29.log.error(
6184
6221
  `PocketBase superuser credentials are required.