vela 0.13.3 → 0.13.4
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 +169 -162
- package/dist/bin.js.map +4 -4
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -21,7 +21,7 @@ import pc42 from "picocolors";
|
|
|
21
21
|
// package.json
|
|
22
22
|
var package_default = {
|
|
23
23
|
name: "vela",
|
|
24
|
-
version: "0.13.
|
|
24
|
+
version: "0.13.4",
|
|
25
25
|
type: "module",
|
|
26
26
|
description: "A CLI for creating and updating SvelteKit projects",
|
|
27
27
|
license: "MIT",
|
|
@@ -57,7 +57,7 @@ var package_default = {
|
|
|
57
57
|
dependencies: {
|
|
58
58
|
"@clack/prompts": "^1.7.0",
|
|
59
59
|
"@faker-js/faker": "^10.6.0",
|
|
60
|
-
"@velastack/patterns": "^0.2.
|
|
60
|
+
"@velastack/patterns": "^0.2.11",
|
|
61
61
|
"@velastack/pocketbase-codegen": "^0.1.0",
|
|
62
62
|
"annotate-json-schema": "^0.1.0",
|
|
63
63
|
commander: "^13.1.0",
|
|
@@ -236,8 +236,8 @@ function stubCommand(name, description, fullName) {
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
// src/lib/workspace.ts
|
|
239
|
-
import
|
|
240
|
-
import
|
|
239
|
+
import fs3 from "node:fs";
|
|
240
|
+
import path2 from "node:path";
|
|
241
241
|
import process3 from "node:process";
|
|
242
242
|
|
|
243
243
|
// src/lib/constants.ts
|
|
@@ -254,8 +254,36 @@ var API_URL = (process2.env.VELA_API_URL?.trim() || "https://velastack.dev").rep
|
|
|
254
254
|
var FIXTURE_PREFIX = "vela";
|
|
255
255
|
var TEMPLATE_INDEX_URL = process2.env.VELA_TEMPLATE_INDEX_URL?.trim() || "https://templates.velastack.app/index.json";
|
|
256
256
|
|
|
257
|
-
// src/lib/
|
|
257
|
+
// src/lib/components-json.ts
|
|
258
258
|
import fs from "node:fs";
|
|
259
|
+
import path from "node:path";
|
|
260
|
+
function readComponentsJson(root) {
|
|
261
|
+
const file = path.join(root, "components.json");
|
|
262
|
+
if (!fs.existsSync(file)) return void 0;
|
|
263
|
+
try {
|
|
264
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
265
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function componentsJsonHints(config) {
|
|
271
|
+
const hints = [];
|
|
272
|
+
if (!config.style) {
|
|
273
|
+
hints.push(
|
|
274
|
+
'components.json has no "style": shadcn-svelte defaults to "nova", while vela ships "vega". Add "style": "vega" so new components match.'
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (!config.iconLibrary) {
|
|
278
|
+
hints.push(
|
|
279
|
+
`components.json has no "iconLibrary": add "iconLibrary": "lucide", the library vela's components use.`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return hints;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/lib/package-json.ts
|
|
286
|
+
import fs2 from "node:fs";
|
|
259
287
|
var DEP_KINDS = ["dependencies", "devDependencies"];
|
|
260
288
|
function mergePackageJson(user, template) {
|
|
261
289
|
const merged = structuredClone(user);
|
|
@@ -316,7 +344,7 @@ function dropTemplateAdapters(user, template) {
|
|
|
316
344
|
return copy;
|
|
317
345
|
}
|
|
318
346
|
function readPackageJson(path48) {
|
|
319
|
-
return JSON.parse(
|
|
347
|
+
return JSON.parse(fs2.readFileSync(path48, "utf8"));
|
|
320
348
|
}
|
|
321
349
|
var PACKAGE_NAME_PLACEHOLDER = /~TODO~/g;
|
|
322
350
|
var APP_NAME_PLACEHOLDER = /~APP_NAME~/g;
|
|
@@ -335,11 +363,11 @@ function escapeSingleQuoted(value) {
|
|
|
335
363
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
336
364
|
}
|
|
337
365
|
function readTemplatePackageJson(path48, values) {
|
|
338
|
-
const raw = fillTemplatePlaceholders(
|
|
366
|
+
const raw = fillTemplatePlaceholders(fs2.readFileSync(path48, "utf8"), values);
|
|
339
367
|
return JSON.parse(raw);
|
|
340
368
|
}
|
|
341
369
|
function writePackageJson(path48, pkg) {
|
|
342
|
-
|
|
370
|
+
fs2.writeFileSync(path48, JSON.stringify(pkg, null, " ") + "\n");
|
|
343
371
|
}
|
|
344
372
|
function toValidPackageName(name) {
|
|
345
373
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9~.-]+/g, "-");
|
|
@@ -353,44 +381,40 @@ function sortKeys(obj) {
|
|
|
353
381
|
// src/lib/workspace.ts
|
|
354
382
|
function findWorkspaceRoot(from = process3.cwd()) {
|
|
355
383
|
let currentDir = from;
|
|
356
|
-
while (currentDir !==
|
|
357
|
-
if (
|
|
358
|
-
currentDir =
|
|
384
|
+
while (currentDir !== path2.parse(currentDir).root) {
|
|
385
|
+
if (fs3.existsSync(path2.join(currentDir, "package.json"))) return currentDir;
|
|
386
|
+
currentDir = path2.dirname(currentDir);
|
|
359
387
|
}
|
|
360
388
|
return null;
|
|
361
389
|
}
|
|
362
390
|
function hasBackend(from = process3.cwd()) {
|
|
363
391
|
const root = findWorkspaceRoot(from);
|
|
364
|
-
return root !== null &&
|
|
392
|
+
return root !== null && fs3.existsSync(path2.join(root, DATA_DIR));
|
|
365
393
|
}
|
|
366
394
|
function hasApiRoutes(root) {
|
|
367
|
-
const dir =
|
|
368
|
-
if (!
|
|
369
|
-
return
|
|
395
|
+
const dir = path2.join(root, "src", "routes", "api");
|
|
396
|
+
if (!fs3.existsSync(dir)) return false;
|
|
397
|
+
return fs3.readdirSync(dir).some((entry) => entry !== "README.md");
|
|
370
398
|
}
|
|
371
399
|
function localDataDir(from = process3.cwd()) {
|
|
372
|
-
return
|
|
400
|
+
return path2.join(findWorkspaceRoot(from) ?? from, DATA_DIR);
|
|
373
401
|
}
|
|
374
402
|
async function getWorkspace() {
|
|
375
403
|
const workspaceRootDir = findWorkspaceRoot();
|
|
376
404
|
if (!workspaceRootDir) {
|
|
377
405
|
throw new Error("Could not find workspace root (no package.json found)");
|
|
378
406
|
}
|
|
379
|
-
const routesDir =
|
|
380
|
-
const fullRoutesPath =
|
|
381
|
-
if (!
|
|
407
|
+
const routesDir = path2.join("src", "routes");
|
|
408
|
+
const fullRoutesPath = path2.join(workspaceRootDir, routesDir);
|
|
409
|
+
if (!fs3.existsSync(fullRoutesPath)) {
|
|
382
410
|
throw new Error("Could not find src/routes directory");
|
|
383
411
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const isAppMode = fs2.existsSync(appRoutesPath);
|
|
391
|
-
if (isAppMode) appRoutesDir = path.join(routesDir, APP_DIR);
|
|
392
|
-
const isPaymentsMode = fs2.existsSync(
|
|
393
|
-
path.join(workspaceRootDir, routesDir, "webhooks", "stripe")
|
|
412
|
+
const routeGroups = detectRouteGroups(workspaceRootDir);
|
|
413
|
+
const publicRoutesDir = path2.join(routesDir, routeGroups.public ?? "");
|
|
414
|
+
const isAppMode = routeGroups.app !== null;
|
|
415
|
+
const appRoutesDir = routeGroups.app ? path2.join(routesDir, routeGroups.app) : void 0;
|
|
416
|
+
const isPaymentsMode = fs3.existsSync(
|
|
417
|
+
path2.join(workspaceRootDir, routesDir, "webhooks", "stripe")
|
|
394
418
|
);
|
|
395
419
|
const features = detectFeatures(workspaceRootDir, { isAppMode, isPaymentsMode });
|
|
396
420
|
return {
|
|
@@ -398,14 +422,25 @@ async function getWorkspace() {
|
|
|
398
422
|
routesDir,
|
|
399
423
|
publicRoutesDir,
|
|
400
424
|
appRoutesDir,
|
|
425
|
+
routeGroups,
|
|
401
426
|
isAppMode,
|
|
402
427
|
isPaymentsMode,
|
|
403
428
|
features
|
|
404
429
|
};
|
|
405
430
|
}
|
|
431
|
+
function detectRouteGroups(root) {
|
|
432
|
+
const group8 = (name) => fs3.existsSync(path2.join(root, "src", "routes", name)) ? name : null;
|
|
433
|
+
return { public: group8(PUBLIC_DIR), app: group8(APP_DIR) };
|
|
434
|
+
}
|
|
435
|
+
function detectUi(root) {
|
|
436
|
+
const pkg = readPackageJson(path2.join(root, "package.json"));
|
|
437
|
+
const hasDep = (name) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
|
|
438
|
+
const shadcn = readComponentsJson(root) !== void 0 && (hasDep("shadcn-svelte") || hasDep("bits-ui"));
|
|
439
|
+
return shadcn ? "shadcn" : "plain";
|
|
440
|
+
}
|
|
406
441
|
function detectFeatures(root, { isAppMode, isPaymentsMode }) {
|
|
407
|
-
const has = (rel) =>
|
|
408
|
-
const pkg = readPackageJson(
|
|
442
|
+
const has = (rel) => fs3.existsSync(path2.join(root, rel));
|
|
443
|
+
const pkg = readPackageJson(path2.join(root, "package.json"));
|
|
409
444
|
const hasDep = (name) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
|
|
410
445
|
return {
|
|
411
446
|
auth: isAppMode,
|
|
@@ -418,7 +453,8 @@ function detectFeatures(root, { isAppMode, isPaymentsMode }) {
|
|
|
418
453
|
blog: hasDep("mdsvex"),
|
|
419
454
|
contentNegotiation: hasDep("sveltekit-negotiate"),
|
|
420
455
|
cms: hasDep("@velastack/cms"),
|
|
421
|
-
workflows: has("src/lib/server/workflows.ts")
|
|
456
|
+
workflows: has("src/lib/server/workflows.ts"),
|
|
457
|
+
ui: detectUi(root)
|
|
422
458
|
};
|
|
423
459
|
}
|
|
424
460
|
|
|
@@ -460,46 +496,46 @@ function toFlag(key) {
|
|
|
460
496
|
}
|
|
461
497
|
|
|
462
498
|
// src/lib/templates.ts
|
|
463
|
-
import
|
|
464
|
-
import
|
|
499
|
+
import fs7 from "node:fs";
|
|
500
|
+
import path6 from "node:path";
|
|
465
501
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
466
502
|
|
|
467
503
|
// src/lib/template-files.ts
|
|
468
|
-
import
|
|
469
|
-
import
|
|
504
|
+
import fs4 from "node:fs";
|
|
505
|
+
import path3 from "node:path";
|
|
470
506
|
var PUBLISH_SAFE_NAMES = {
|
|
471
507
|
".gitignore": "_gitignore",
|
|
472
508
|
".npmrc": "_npmrc"
|
|
473
509
|
};
|
|
474
510
|
function templateName(projectRelPath) {
|
|
475
|
-
const safe = PUBLISH_SAFE_NAMES[
|
|
511
|
+
const safe = PUBLISH_SAFE_NAMES[path3.basename(projectRelPath)];
|
|
476
512
|
if (!safe) return projectRelPath;
|
|
477
|
-
const dir =
|
|
478
|
-
return dir === "." ? safe :
|
|
513
|
+
const dir = path3.dirname(projectRelPath);
|
|
514
|
+
return dir === "." ? safe : path3.join(dir, safe);
|
|
479
515
|
}
|
|
480
516
|
function restoreTemplateNames(target) {
|
|
481
517
|
for (const [real, safe] of Object.entries(PUBLISH_SAFE_NAMES)) {
|
|
482
|
-
const from =
|
|
483
|
-
if (!
|
|
484
|
-
|
|
518
|
+
const from = path3.join(target, safe);
|
|
519
|
+
if (!fs4.existsSync(from)) continue;
|
|
520
|
+
fs4.renameSync(from, path3.join(target, real));
|
|
485
521
|
}
|
|
486
522
|
}
|
|
487
523
|
var TEMPLATE_SOURCE = /\.template\.([^.]+)$/;
|
|
488
524
|
function applyTemplateFiles(target, values) {
|
|
489
525
|
const written = [];
|
|
490
526
|
for (const source of findTemplateSources(target)) {
|
|
491
|
-
const raw =
|
|
527
|
+
const raw = fs4.readFileSync(source, "utf8");
|
|
492
528
|
const dest = source.replace(TEMPLATE_SOURCE, ".$1");
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
written.push(
|
|
529
|
+
fs4.writeFileSync(dest, fillTemplatePlaceholders(raw, values));
|
|
530
|
+
fs4.unlinkSync(source);
|
|
531
|
+
written.push(path3.relative(target, dest));
|
|
496
532
|
}
|
|
497
533
|
return written.sort();
|
|
498
534
|
}
|
|
499
535
|
function findTemplateSources(dir) {
|
|
500
536
|
const found = [];
|
|
501
|
-
for (const entry of
|
|
502
|
-
const full =
|
|
537
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
538
|
+
const full = path3.join(dir, entry.name);
|
|
503
539
|
if (entry.isDirectory()) {
|
|
504
540
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
505
541
|
found.push(...findTemplateSources(full));
|
|
@@ -511,38 +547,38 @@ function findTemplateSources(dir) {
|
|
|
511
547
|
}
|
|
512
548
|
|
|
513
549
|
// src/lib/template-registry.ts
|
|
514
|
-
import
|
|
550
|
+
import fs6 from "node:fs";
|
|
515
551
|
import os2 from "node:os";
|
|
516
|
-
import
|
|
552
|
+
import path5 from "node:path";
|
|
517
553
|
import { createHash } from "node:crypto";
|
|
518
554
|
import { fileURLToPath } from "node:url";
|
|
519
555
|
import * as v2 from "valibot";
|
|
520
556
|
import * as tar from "tar";
|
|
521
557
|
|
|
522
558
|
// src/lib/config.ts
|
|
523
|
-
import
|
|
559
|
+
import fs5 from "node:fs";
|
|
524
560
|
import os from "node:os";
|
|
525
|
-
import
|
|
561
|
+
import path4 from "node:path";
|
|
526
562
|
import process4 from "node:process";
|
|
527
563
|
function configDir() {
|
|
528
|
-
return
|
|
564
|
+
return path4.join(os.homedir(), ".vela");
|
|
529
565
|
}
|
|
530
566
|
var CONFIG_DIR = configDir();
|
|
531
|
-
var CONFIG_PATH =
|
|
567
|
+
var CONFIG_PATH = path4.join(CONFIG_DIR, "config.json");
|
|
532
568
|
function readConfig() {
|
|
533
|
-
if (!
|
|
569
|
+
if (!fs5.existsSync(CONFIG_PATH)) return null;
|
|
534
570
|
try {
|
|
535
|
-
return JSON.parse(
|
|
571
|
+
return JSON.parse(fs5.readFileSync(CONFIG_PATH, "utf8"));
|
|
536
572
|
} catch {
|
|
537
573
|
return null;
|
|
538
574
|
}
|
|
539
575
|
}
|
|
540
576
|
function writeConfig(config) {
|
|
541
|
-
|
|
542
|
-
|
|
577
|
+
fs5.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
578
|
+
fs5.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
543
579
|
}
|
|
544
580
|
function clearConfig() {
|
|
545
|
-
if (
|
|
581
|
+
if (fs5.existsSync(CONFIG_PATH)) fs5.unlinkSync(CONFIG_PATH);
|
|
546
582
|
}
|
|
547
583
|
function readApiKey() {
|
|
548
584
|
const fromEnv = process4.env.VELA_API_KEY?.trim();
|
|
@@ -588,16 +624,16 @@ var indexSchema = v2.object({
|
|
|
588
624
|
templates: v2.array(v2.unknown())
|
|
589
625
|
});
|
|
590
626
|
function cacheRoot() {
|
|
591
|
-
return
|
|
627
|
+
return path5.join(configDir(), CACHE_DIR_NAME);
|
|
592
628
|
}
|
|
593
629
|
function indexCachePath() {
|
|
594
|
-
return
|
|
630
|
+
return path5.join(cacheRoot(), INDEX_CACHE_FILE);
|
|
595
631
|
}
|
|
596
632
|
function readCachedIndex(url) {
|
|
597
633
|
const file = indexCachePath();
|
|
598
|
-
if (!
|
|
634
|
+
if (!fs6.existsSync(file)) return null;
|
|
599
635
|
try {
|
|
600
|
-
const cached2 = JSON.parse(
|
|
636
|
+
const cached2 = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
601
637
|
if (cached2.url !== url) return null;
|
|
602
638
|
return cached2;
|
|
603
639
|
} catch {
|
|
@@ -605,8 +641,8 @@ function readCachedIndex(url) {
|
|
|
605
641
|
}
|
|
606
642
|
}
|
|
607
643
|
function writeCachedIndex(cached2) {
|
|
608
|
-
|
|
609
|
-
|
|
644
|
+
fs6.mkdirSync(cacheRoot(), { recursive: true });
|
|
645
|
+
fs6.writeFileSync(indexCachePath(), JSON.stringify(cached2, null, 2));
|
|
610
646
|
}
|
|
611
647
|
function parseTemplateIndex(text19, url) {
|
|
612
648
|
let parsed;
|
|
@@ -633,7 +669,7 @@ function isFileUrl(url) {
|
|
|
633
669
|
}
|
|
634
670
|
async function readBytes(url, options) {
|
|
635
671
|
if (isFileUrl(url)) {
|
|
636
|
-
return new Uint8Array(
|
|
672
|
+
return new Uint8Array(fs6.readFileSync(fileURLToPath(url)));
|
|
637
673
|
}
|
|
638
674
|
const res = await fetch(url, {
|
|
639
675
|
headers: options.headers,
|
|
@@ -671,7 +707,7 @@ function sha256(bytes) {
|
|
|
671
707
|
return createHash("sha256").update(bytes).digest("hex");
|
|
672
708
|
}
|
|
673
709
|
function tarballCachePath(entry) {
|
|
674
|
-
return
|
|
710
|
+
return path5.join(
|
|
675
711
|
cacheRoot(),
|
|
676
712
|
TARBALL_CACHE_DIR,
|
|
677
713
|
`${entry.name}-${entry.sha256.slice(0, 12)}.tgz`
|
|
@@ -681,8 +717,8 @@ async function downloadTemplate(entry, indexUrl, options = {}) {
|
|
|
681
717
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS * 6;
|
|
682
718
|
const cacheFile = tarballCachePath(entry);
|
|
683
719
|
let haveTarball = false;
|
|
684
|
-
if (
|
|
685
|
-
const cached2 = new Uint8Array(
|
|
720
|
+
if (fs6.existsSync(cacheFile)) {
|
|
721
|
+
const cached2 = new Uint8Array(fs6.readFileSync(cacheFile));
|
|
686
722
|
haveTarball = sha256(cached2) === entry.sha256;
|
|
687
723
|
}
|
|
688
724
|
if (!haveTarball) {
|
|
@@ -699,14 +735,14 @@ async function downloadTemplate(entry, indexUrl, options = {}) {
|
|
|
699
735
|
`Downloaded template ${entry.name} did not match its checksum (expected ${entry.sha256}, got ${digest}). Try again, or set VELA_TEMPLATE_INDEX_URL to a registry you trust.`
|
|
700
736
|
);
|
|
701
737
|
}
|
|
702
|
-
|
|
703
|
-
|
|
738
|
+
fs6.mkdirSync(path5.dirname(cacheFile), { recursive: true });
|
|
739
|
+
fs6.writeFileSync(cacheFile, fetched);
|
|
704
740
|
}
|
|
705
|
-
const dir =
|
|
741
|
+
const dir = fs6.mkdtempSync(path5.join(os2.tmpdir(), `vela-template-${entry.name}-`));
|
|
706
742
|
try {
|
|
707
743
|
await tar.extract({ file: cacheFile, cwd: dir, strip: 1 });
|
|
708
744
|
} catch (e) {
|
|
709
|
-
|
|
745
|
+
fs6.rmSync(dir, { recursive: true, force: true });
|
|
710
746
|
throw new Error(`Could not unpack template ${entry.name}: ${e.message}`);
|
|
711
747
|
}
|
|
712
748
|
return dir;
|
|
@@ -718,25 +754,25 @@ var DEFAULT_TEMPLATE = "minimal";
|
|
|
718
754
|
var DEFAULT_CATEGORY = "starter";
|
|
719
755
|
var cached;
|
|
720
756
|
function templatesDir() {
|
|
721
|
-
let dir =
|
|
722
|
-
const { root } =
|
|
757
|
+
let dir = path6.dirname(fileURLToPath2(import.meta.url));
|
|
758
|
+
const { root } = path6.parse(dir);
|
|
723
759
|
while (dir !== root) {
|
|
724
|
-
const candidate =
|
|
760
|
+
const candidate = path6.join(dir, "templates");
|
|
725
761
|
if (holdsManifest(candidate)) return candidate;
|
|
726
|
-
dir =
|
|
762
|
+
dir = path6.dirname(dir);
|
|
727
763
|
}
|
|
728
764
|
throw new Error("Could not locate the templates directory");
|
|
729
765
|
}
|
|
730
766
|
function holdsManifest(dir) {
|
|
731
|
-
if (!
|
|
732
|
-
return
|
|
733
|
-
(entry) => entry.isDirectory() &&
|
|
767
|
+
if (!fs7.existsSync(dir)) return false;
|
|
768
|
+
return fs7.readdirSync(dir, { withFileTypes: true }).some(
|
|
769
|
+
(entry) => entry.isDirectory() && fs7.existsSync(path6.join(dir, entry.name, TEMPLATE_MANIFEST))
|
|
734
770
|
);
|
|
735
771
|
}
|
|
736
772
|
function listProjectTemplates() {
|
|
737
773
|
if (cached) return cached;
|
|
738
774
|
const root = templatesDir();
|
|
739
|
-
cached =
|
|
775
|
+
cached = fs7.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));
|
|
740
776
|
return cached;
|
|
741
777
|
}
|
|
742
778
|
function projectTemplateNames(options = {}) {
|
|
@@ -809,25 +845,25 @@ async function resolveTemplate(template) {
|
|
|
809
845
|
...info,
|
|
810
846
|
dir,
|
|
811
847
|
cleanup() {
|
|
812
|
-
|
|
848
|
+
fs7.rmSync(dir, { recursive: true, force: true });
|
|
813
849
|
}
|
|
814
850
|
};
|
|
815
851
|
}
|
|
816
852
|
function copyTemplate(template, target) {
|
|
817
|
-
|
|
818
|
-
|
|
853
|
+
fs7.mkdirSync(target, { recursive: true });
|
|
854
|
+
fs7.cpSync(template.dir, target, {
|
|
819
855
|
recursive: true,
|
|
820
856
|
// The manifest describes the template to the CLI; it isn't part of the project.
|
|
821
|
-
filter: (src) =>
|
|
857
|
+
filter: (src) => path6.basename(src) !== ".DS_Store" && path6.relative(template.dir, src) !== TEMPLATE_MANIFEST
|
|
822
858
|
});
|
|
823
859
|
restoreTemplateNames(target);
|
|
824
860
|
}
|
|
825
861
|
function readManifest(root, name) {
|
|
826
|
-
const manifestPath =
|
|
827
|
-
if (!
|
|
862
|
+
const manifestPath = path6.join(root, name, TEMPLATE_MANIFEST);
|
|
863
|
+
if (!fs7.existsSync(manifestPath)) return void 0;
|
|
828
864
|
let parsed;
|
|
829
865
|
try {
|
|
830
|
-
parsed = JSON.parse(
|
|
866
|
+
parsed = JSON.parse(fs7.readFileSync(manifestPath, "utf8"));
|
|
831
867
|
} catch (e) {
|
|
832
868
|
throw new Error(
|
|
833
869
|
`Template ${name} has an unreadable ${TEMPLATE_MANIFEST}: ${e.message}`
|
|
@@ -850,7 +886,7 @@ function readManifest(root, name) {
|
|
|
850
886
|
price: typeof manifest.price === "number" ? manifest.price : void 0,
|
|
851
887
|
nextSteps: optionalStringArray(manifest.nextSteps),
|
|
852
888
|
cms: typeof manifest.cms === "boolean" ? manifest.cms : void 0,
|
|
853
|
-
dir:
|
|
889
|
+
dir: path6.join(root, name)
|
|
854
890
|
};
|
|
855
891
|
}
|
|
856
892
|
function optionalString(value) {
|
|
@@ -863,8 +899,8 @@ function optionalStringArray(value) {
|
|
|
863
899
|
}
|
|
864
900
|
|
|
865
901
|
// src/lib/package-manager.ts
|
|
866
|
-
import
|
|
867
|
-
import
|
|
902
|
+
import fs8 from "node:fs";
|
|
903
|
+
import path7 from "node:path";
|
|
868
904
|
import process5 from "node:process";
|
|
869
905
|
import { exec } from "tinyexec";
|
|
870
906
|
import { Option } from "commander";
|
|
@@ -934,9 +970,9 @@ async function installDependencies(agent, cwd, { exitOnFailure = true } = {}) {
|
|
|
934
970
|
}
|
|
935
971
|
function addPnpmBuildDependencies(cwd, packageManager2, allowedPackages) {
|
|
936
972
|
if (!packageManager2 || packageManager2 !== "pnpm") return;
|
|
937
|
-
const pkgPath =
|
|
938
|
-
if (!
|
|
939
|
-
const pkg = JSON.parse(
|
|
973
|
+
const pkgPath = path7.join(cwd, "package.json");
|
|
974
|
+
if (!fs8.existsSync(pkgPath)) return;
|
|
975
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
940
976
|
pkg.pnpm ??= {};
|
|
941
977
|
pkg.pnpm.onlyBuiltDependencies ??= [];
|
|
942
978
|
for (const name of allowedPackages) {
|
|
@@ -944,13 +980,13 @@ function addPnpmBuildDependencies(cwd, packageManager2, allowedPackages) {
|
|
|
944
980
|
pkg.pnpm.onlyBuiltDependencies.push(name);
|
|
945
981
|
}
|
|
946
982
|
}
|
|
947
|
-
|
|
983
|
+
fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, " ") + "\n");
|
|
948
984
|
}
|
|
949
985
|
|
|
950
986
|
// src/lib/pocketbase.ts
|
|
951
|
-
import
|
|
987
|
+
import fs9 from "node:fs";
|
|
952
988
|
import net from "node:net";
|
|
953
|
-
import
|
|
989
|
+
import path8 from "node:path";
|
|
954
990
|
import process6 from "node:process";
|
|
955
991
|
import { createRequire } from "node:module";
|
|
956
992
|
import { spawn } from "node:child_process";
|
|
@@ -1063,9 +1099,9 @@ async function authWithRetries(pb, email3, password11, attempts = 3) {
|
|
|
1063
1099
|
}
|
|
1064
1100
|
}
|
|
1065
1101
|
function getPocketbaseMetadata(cwd) {
|
|
1066
|
-
const metadataPath =
|
|
1067
|
-
if (
|
|
1068
|
-
return JSON.parse(
|
|
1102
|
+
const metadataPath = path8.join(cwd, "node_modules", ".vite", "_pocketbase_metadata.json");
|
|
1103
|
+
if (fs9.existsSync(metadataPath)) {
|
|
1104
|
+
return JSON.parse(fs9.readFileSync(metadataPath, "utf8"));
|
|
1069
1105
|
}
|
|
1070
1106
|
return null;
|
|
1071
1107
|
}
|
|
@@ -1078,12 +1114,12 @@ async function execPackageBin(cwd, args, stdio = "pipe") {
|
|
|
1078
1114
|
return x(command, resolvedArgs, { nodeOptions: { cwd, stdio }, throwOnError: true });
|
|
1079
1115
|
}
|
|
1080
1116
|
async function withPocketbase(cwd, fn, creds) {
|
|
1081
|
-
const dir =
|
|
1082
|
-
const migrationsDir =
|
|
1117
|
+
const dir = path8.join(cwd, DATA_DIR);
|
|
1118
|
+
const migrationsDir = path8.join(cwd, MIGRATIONS_DIR);
|
|
1083
1119
|
const host = "localhost";
|
|
1084
1120
|
const email3 = creds?.email ?? process6.env.POCKETBASE_SUPERUSER_EMAIL;
|
|
1085
1121
|
const password11 = creds?.password ?? process6.env.POCKETBASE_SUPERUSER_PASSWORD;
|
|
1086
|
-
if (!
|
|
1122
|
+
if (!fs9.existsSync(dir)) {
|
|
1087
1123
|
throw new Error("PocketBase data directory does not exist");
|
|
1088
1124
|
}
|
|
1089
1125
|
const metadata = getPocketbaseMetadata(cwd);
|
|
@@ -1108,10 +1144,10 @@ async function withPocketbase(cwd, fn, creds) {
|
|
|
1108
1144
|
}
|
|
1109
1145
|
}
|
|
1110
1146
|
async function createSuperuser(cwd, email3, password11) {
|
|
1111
|
-
const dir =
|
|
1112
|
-
const migrationsDir =
|
|
1113
|
-
|
|
1114
|
-
|
|
1147
|
+
const dir = path8.join(cwd, DATA_DIR);
|
|
1148
|
+
const migrationsDir = path8.join(cwd, MIGRATIONS_DIR);
|
|
1149
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1150
|
+
fs9.mkdirSync(migrationsDir, { recursive: true });
|
|
1115
1151
|
await execPackageBin(
|
|
1116
1152
|
cwd,
|
|
1117
1153
|
[
|
|
@@ -1136,8 +1172,8 @@ async function launchPocketbase(cwd, {
|
|
|
1136
1172
|
password: password11
|
|
1137
1173
|
}) {
|
|
1138
1174
|
const host = "localhost";
|
|
1139
|
-
|
|
1140
|
-
|
|
1175
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1176
|
+
fs9.mkdirSync(migrationsDir, { recursive: true });
|
|
1141
1177
|
await execPackageBin(
|
|
1142
1178
|
cwd,
|
|
1143
1179
|
[
|
|
@@ -1179,8 +1215,8 @@ async function ensureSuperuser(cwd) {
|
|
|
1179
1215
|
const email3 = process6.env.POCKETBASE_SUPERUSER_EMAIL;
|
|
1180
1216
|
const password11 = process6.env.POCKETBASE_SUPERUSER_PASSWORD;
|
|
1181
1217
|
if (!email3 || !password11) return;
|
|
1182
|
-
const dir =
|
|
1183
|
-
if (!
|
|
1218
|
+
const dir = path8.join(cwd, DATA_DIR);
|
|
1219
|
+
if (!fs9.existsSync(dir)) return;
|
|
1184
1220
|
const { getBinaryPath } = await import("pocketbase-server");
|
|
1185
1221
|
await x(
|
|
1186
1222
|
getBinaryPath(),
|
|
@@ -1188,7 +1224,7 @@ async function ensureSuperuser(cwd) {
|
|
|
1188
1224
|
"--dir",
|
|
1189
1225
|
dir,
|
|
1190
1226
|
"--migrationsDir",
|
|
1191
|
-
|
|
1227
|
+
path8.join(cwd, MIGRATIONS_DIR),
|
|
1192
1228
|
"superuser",
|
|
1193
1229
|
"upsert",
|
|
1194
1230
|
email3,
|
|
@@ -1199,8 +1235,8 @@ async function ensureSuperuser(cwd) {
|
|
|
1199
1235
|
}
|
|
1200
1236
|
|
|
1201
1237
|
// src/lib/env.ts
|
|
1202
|
-
import
|
|
1203
|
-
import
|
|
1238
|
+
import fs10 from "node:fs";
|
|
1239
|
+
import path9 from "node:path";
|
|
1204
1240
|
function addEnvVar(content, key, value) {
|
|
1205
1241
|
if (content.includes(`${key}=`)) return content;
|
|
1206
1242
|
return appendLine(content, `${key}=${value}`);
|
|
@@ -1215,11 +1251,11 @@ function appendLine(existing, line) {
|
|
|
1215
1251
|
return withNewline + line + "\n";
|
|
1216
1252
|
}
|
|
1217
1253
|
function writeEnvFile(cwd, vars, comments = []) {
|
|
1218
|
-
const envPath =
|
|
1219
|
-
let content =
|
|
1254
|
+
const envPath = path9.join(cwd, ".env");
|
|
1255
|
+
let content = fs10.existsSync(envPath) ? fs10.readFileSync(envPath, "utf8") : "";
|
|
1220
1256
|
for (const comment of comments) content = addEnvComment(content, comment);
|
|
1221
1257
|
for (const [key, value] of Object.entries(vars)) content = addEnvVar(content, key, value);
|
|
1222
|
-
|
|
1258
|
+
fs10.writeFileSync(envPath, content);
|
|
1223
1259
|
}
|
|
1224
1260
|
function upsertEnvVar(content, key, value) {
|
|
1225
1261
|
const line = `${key}=${quoteEnvValue(value)}`;
|
|
@@ -1252,7 +1288,7 @@ function quoteEnvValue(value) {
|
|
|
1252
1288
|
}
|
|
1253
1289
|
|
|
1254
1290
|
// src/lib/app-css.ts
|
|
1255
|
-
import
|
|
1291
|
+
import fs11 from "node:fs";
|
|
1256
1292
|
var SHADCN_TAILWIND_CSS = "shadcn-svelte/tailwind.css";
|
|
1257
1293
|
var SHADCN_TAILWIND_IMPORT = `@import '${SHADCN_TAILWIND_CSS}';`;
|
|
1258
1294
|
function hasShadcnImport(css) {
|
|
@@ -1278,41 +1314,13 @@ ${css}`;
|
|
|
1278
1314
|
return lines.join("\n");
|
|
1279
1315
|
}
|
|
1280
1316
|
function ensureShadcnImport(appCssPath) {
|
|
1281
|
-
if (!
|
|
1282
|
-
const css =
|
|
1317
|
+
if (!fs11.existsSync(appCssPath)) return false;
|
|
1318
|
+
const css = fs11.readFileSync(appCssPath, "utf8");
|
|
1283
1319
|
if (hasShadcnImport(css)) return false;
|
|
1284
|
-
|
|
1320
|
+
fs11.writeFileSync(appCssPath, insertShadcnImport(css));
|
|
1285
1321
|
return true;
|
|
1286
1322
|
}
|
|
1287
1323
|
|
|
1288
|
-
// src/lib/components-json.ts
|
|
1289
|
-
import fs11 from "node:fs";
|
|
1290
|
-
import path9 from "node:path";
|
|
1291
|
-
function readComponentsJson(root) {
|
|
1292
|
-
const file = path9.join(root, "components.json");
|
|
1293
|
-
if (!fs11.existsSync(file)) return void 0;
|
|
1294
|
-
try {
|
|
1295
|
-
const parsed = JSON.parse(fs11.readFileSync(file, "utf8"));
|
|
1296
|
-
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
1297
|
-
} catch {
|
|
1298
|
-
return void 0;
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
function componentsJsonHints(config) {
|
|
1302
|
-
const hints = [];
|
|
1303
|
-
if (!config.style) {
|
|
1304
|
-
hints.push(
|
|
1305
|
-
'components.json has no "style": shadcn-svelte defaults to "nova", while vela ships "vega". Add "style": "vega" so new components match.'
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
if (!config.iconLibrary) {
|
|
1309
|
-
hints.push(
|
|
1310
|
-
`components.json has no "iconLibrary": add "iconLibrary": "lucide", the library vela's components use.`
|
|
1311
|
-
);
|
|
1312
|
-
}
|
|
1313
|
-
return hints;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
1324
|
// src/lib/config-merge.ts
|
|
1317
1325
|
import fs13 from "node:fs";
|
|
1318
1326
|
import path11 from "node:path";
|
|
@@ -2691,7 +2699,7 @@ async function runPattern(slug2, argv, input, report4) {
|
|
|
2691
2699
|
throw new Error(`Unknown pattern: ${slug2}`);
|
|
2692
2700
|
}
|
|
2693
2701
|
checkProviderInput(pattern, argv, input);
|
|
2694
|
-
const { workspaceRootDir, features } = await getWorkspace();
|
|
2702
|
+
const { workspaceRootDir, features, routeGroups } = await getWorkspace();
|
|
2695
2703
|
const log50 = p9.taskLog({ title: report4.task.title });
|
|
2696
2704
|
let result;
|
|
2697
2705
|
try {
|
|
@@ -2700,6 +2708,7 @@ async function runPattern(slug2, argv, input, report4) {
|
|
|
2700
2708
|
env: "runtime",
|
|
2701
2709
|
root: workspaceRootDir,
|
|
2702
2710
|
features,
|
|
2711
|
+
routeGroups,
|
|
2703
2712
|
input,
|
|
2704
2713
|
// Patterns no longer read the schema themselves: @velastack/pocketbase-codegen
|
|
2705
2714
|
// takes an injected client, and only the CLI knows how to reach (or spawn)
|
|
@@ -2751,24 +2760,22 @@ async function runPattern(slug2, argv, input, report4) {
|
|
|
2751
2760
|
|
|
2752
2761
|
// src/lib/form-ui.ts
|
|
2753
2762
|
import path17 from "node:path";
|
|
2754
|
-
var
|
|
2763
|
+
var UIS = ["shadcn", "plain"];
|
|
2755
2764
|
function detectFormInput(root) {
|
|
2756
2765
|
const pkg = readPackageJson(path17.join(root, "package.json"));
|
|
2757
2766
|
const hasDep = (name) => Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
|
|
2758
|
-
const shadcn = readComponentsJson(root) !== void 0 && (hasDep("shadcn-svelte") || hasDep("bits-ui"));
|
|
2759
2767
|
return {
|
|
2760
|
-
ui: shadcn ? "shadcn" : "plain",
|
|
2761
2768
|
flash: hasDep("sveltekit-flash-message"),
|
|
2762
2769
|
serverTests: hasDep("supertest")
|
|
2763
2770
|
};
|
|
2764
2771
|
}
|
|
2765
|
-
function resolveFormInput(root, requested) {
|
|
2772
|
+
function resolveFormInput(root, detectedUi, requested) {
|
|
2766
2773
|
const detected = detectFormInput(root);
|
|
2767
2774
|
if (requested === void 0) return detected;
|
|
2768
|
-
if (!
|
|
2769
|
-
throw new Error(`Unknown --ui "${requested}". Expected one of: ${
|
|
2775
|
+
if (!UIS.includes(requested)) {
|
|
2776
|
+
throw new Error(`Unknown --ui "${requested}". Expected one of: ${UIS.join(", ")}.`);
|
|
2770
2777
|
}
|
|
2771
|
-
if (requested === "shadcn" &&
|
|
2778
|
+
if (requested === "shadcn" && detectedUi !== "shadcn") {
|
|
2772
2779
|
throw new Error(
|
|
2773
2780
|
"--ui shadcn needs a shadcn-svelte project (a components.json and the shadcn-svelte package). Run `vela bless` to set one up, or use --ui plain."
|
|
2774
2781
|
);
|
|
@@ -3063,7 +3070,7 @@ function writeLayoutSidecar(workspaceRootDir, modelName, layout) {
|
|
|
3063
3070
|
// src/commands/generate/form.ts
|
|
3064
3071
|
var form = new Command5("form").description("generate a form from a model").argument("[model]", 'model name (e.g. "contact")').argument("[fields...]", 'field definitions (e.g. "name:text", "email:email")').option("--remote", "generate a form backed by a remote PocketBase collection").option(
|
|
3065
3072
|
"--route <route>",
|
|
3066
|
-
'place the form at a custom route (e.g. "(app)/[team_id]/projects/new"). Defaults to the model name under (app)
|
|
3073
|
+
'place the form at a custom route (e.g. "(app)/[team_id]/projects/new"). Defaults to the model name under the (app) or (public) group, or src/routes when it has neither.'
|
|
3067
3074
|
).option(
|
|
3068
3075
|
"--ui <ui>",
|
|
3069
3076
|
'markup to generate: "shadcn" components or "plain" HTML. Defaults to shadcn when the project has shadcn-svelte, plain otherwise.'
|
|
@@ -3099,9 +3106,9 @@ var form = new Command5("form").description("generate a form from a model").argu
|
|
|
3099
3106
|
argv = [model, ...fields];
|
|
3100
3107
|
modelName = model;
|
|
3101
3108
|
}
|
|
3102
|
-
const { workspaceRootDir } = await getWorkspace();
|
|
3103
|
-
const formInput = resolveFormInput(workspaceRootDir, options.ui);
|
|
3104
|
-
if (
|
|
3109
|
+
const { workspaceRootDir, features } = await getWorkspace();
|
|
3110
|
+
const formInput = resolveFormInput(workspaceRootDir, features.ui, options.ui);
|
|
3111
|
+
if (features.ui === "plain" && !options.ui) {
|
|
3105
3112
|
p14.log.info("shadcn-svelte not detected: generating a plain HTML form.");
|
|
3106
3113
|
}
|
|
3107
3114
|
const slug2 = options.remote ? "generate-form-remote" : "generate-form";
|
|
@@ -3207,7 +3214,7 @@ import { Command as Command8 } from "commander";
|
|
|
3207
3214
|
import * as p16 from "@clack/prompts";
|
|
3208
3215
|
var scaffold = new Command8("scaffold").description("generate a full CRUD scaffold (model, forms, list, detail)").argument("[model]", "model name").argument("[fields...]", "field definitions").option("--remote", "generate a scaffold backed by a remote PocketBase collection").option(
|
|
3209
3216
|
"--route <route>",
|
|
3210
|
-
'place the scaffold at a custom route (e.g. "(app)/[team_id]/projects"). Defaults to the pluralized model name under (app)
|
|
3217
|
+
'place the scaffold at a custom route (e.g. "(app)/[team_id]/projects"). Defaults to the pluralized model name under the (app) or (public) group, or src/routes when it has neither.'
|
|
3211
3218
|
).option(
|
|
3212
3219
|
"--ai <description>",
|
|
3213
3220
|
"design the scaffold with AI from a natural-language description (two stages: schema \u2192 layout)"
|