cascivo 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/dist/{audit-AHr4MDMK.mjs → audit-BRn4MeXD.mjs} +8 -8
- package/dist/{audit-AHr4MDMK.mjs.map → audit-BRn4MeXD.mjs.map} +1 -1
- package/dist/config-CsnCR5eh.mjs +82 -0
- package/dist/config-CsnCR5eh.mjs.map +1 -0
- package/dist/{eject-D_mC1lsq.mjs → eject-B92gZhLX.mjs} +3 -3
- package/dist/{eject-D_mC1lsq.mjs.map → eject-B92gZhLX.mjs.map} +1 -1
- package/dist/fs-m7ZvuBBm.mjs +24 -0
- package/dist/fs-m7ZvuBBm.mjs.map +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +590 -120
- package/dist/index.mjs.map +1 -1
- package/dist/template-init-DGEgatij.mjs +179 -0
- package/dist/template-init-DGEgatij.mjs.map +1 -0
- package/package.json +2 -2
- package/readme.body.md +14 -8
package/dist/index.mjs
CHANGED
|
@@ -1,70 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { a as loadConfig, i as installCommand, o as __exportAll, r as detectPackageManager, t as DEFAULT_CONFIG } from "./config-CsnCR5eh.mjs";
|
|
3
|
+
import { n as resolveOutputPath, r as writeFileSafe, t as readFileSafe } from "./fs-m7ZvuBBm.mjs";
|
|
2
4
|
import { t as fetchJson } from "./http-CLkdTpxD.mjs";
|
|
3
5
|
import { argv, stdin, stdout } from "node:process";
|
|
4
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
8
|
import { spawnSync } from "node:child_process";
|
|
6
9
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
|
-
import {
|
|
8
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
9
11
|
import { createHash } from "node:crypto";
|
|
12
|
+
import { asTemplateMeta, buildRegistry, isTemplateItem, parseLegacyRegistry, validateIndex, validateTemplate } from "@cascivo/registry";
|
|
10
13
|
import { createInterface } from "node:readline/promises";
|
|
11
|
-
import { buildRegistry, parseLegacyRegistry, validateIndex } from "@cascivo/registry";
|
|
12
|
-
//#region src/utils/config.ts
|
|
13
|
-
const DEFAULT_CONFIG = {
|
|
14
|
-
registry: "https://cascivo.com/registry.json",
|
|
15
|
-
outputDir: "src/components/ui",
|
|
16
|
-
theme: "light"
|
|
17
|
-
};
|
|
18
|
-
const CONFIG_FILES = [
|
|
19
|
-
"cascivo.config.ts",
|
|
20
|
-
"cascivo.config.js",
|
|
21
|
-
"cascivo.config.mjs"
|
|
22
|
-
];
|
|
23
|
-
/** Apply defaults over a (possibly partial) user config object. */
|
|
24
|
-
function resolveConfig(partial) {
|
|
25
|
-
return {
|
|
26
|
-
...DEFAULT_CONFIG,
|
|
27
|
-
...partial
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/** Let env vars override config — used by the MCP server to pass `outputDir`. */
|
|
31
|
-
function applyEnvOverrides(config, env = process.env) {
|
|
32
|
-
return {
|
|
33
|
-
...config,
|
|
34
|
-
...env.CASCIVO_REGISTRY ? { registry: env.CASCIVO_REGISTRY } : {},
|
|
35
|
-
...env.CASCIVO_OUTPUT_DIR ? { outputDir: env.CASCIVO_OUTPUT_DIR } : {}
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Locate and load `cascivo.config.{ts,js,mjs}` from `cwd`. Falls back to the
|
|
40
|
-
* default config when no file is found or the file cannot be loaded. Env vars
|
|
41
|
-
* (`CASCIVO_REGISTRY`, `CASCIVO_OUTPUT_DIR`) take precedence.
|
|
42
|
-
*/
|
|
43
|
-
async function loadConfig(cwd = process.cwd()) {
|
|
44
|
-
for (const file of CONFIG_FILES) {
|
|
45
|
-
const path = join(cwd, file);
|
|
46
|
-
if (!existsSync(path)) continue;
|
|
47
|
-
try {
|
|
48
|
-
const mod = await import(pathToFileURL(path).href);
|
|
49
|
-
return applyEnvOverrides(resolveConfig(mod.default ?? mod));
|
|
50
|
-
} catch {
|
|
51
|
-
return applyEnvOverrides(resolveConfig(null));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return applyEnvOverrides(resolveConfig(null));
|
|
55
|
-
}
|
|
56
|
-
/** Detect the package manager in use from lock files, defaulting to npm. */
|
|
57
|
-
function detectPackageManager(cwd = process.cwd()) {
|
|
58
|
-
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
59
|
-
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
60
|
-
if (existsSync(join(cwd, "bun.lockb"))) return "bun";
|
|
61
|
-
return "npm";
|
|
62
|
-
}
|
|
63
|
-
/** The install subcommand each package manager uses to add dependencies. */
|
|
64
|
-
function installCommand(pm, packages) {
|
|
65
|
-
return [pm, [pm === "npm" ? "install" : "add", ...packages]];
|
|
66
|
-
}
|
|
67
|
-
//#endregion
|
|
68
14
|
//#region src/utils/exec.ts
|
|
69
15
|
/**
|
|
70
16
|
* Install npm packages using the detected package manager, inheriting stdio so
|
|
@@ -85,25 +31,6 @@ function installPackages(packages, cwd = process.cwd()) {
|
|
|
85
31
|
return true;
|
|
86
32
|
}
|
|
87
33
|
//#endregion
|
|
88
|
-
//#region src/utils/fs.ts
|
|
89
|
-
/** Resolve where a component file should be written. */
|
|
90
|
-
function resolveOutputPath(outputDir, component, file, cwd = process.cwd()) {
|
|
91
|
-
return join(resolve(cwd, outputDir), component, file);
|
|
92
|
-
}
|
|
93
|
-
/** Write a file, creating parent directories as needed. */
|
|
94
|
-
async function writeFileSafe(path, content) {
|
|
95
|
-
await mkdir(dirname(path), { recursive: true });
|
|
96
|
-
await writeFile(path, content, "utf8");
|
|
97
|
-
}
|
|
98
|
-
/** Read a file, returning null if it does not exist. */
|
|
99
|
-
async function readFileSafe(path) {
|
|
100
|
-
try {
|
|
101
|
-
return await readFile(path, "utf8");
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
34
|
//#region src/utils/registry.ts
|
|
108
35
|
function asStringArray(value) {
|
|
109
36
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
@@ -133,6 +60,10 @@ function parseRegistry(raw) {
|
|
|
133
60
|
};
|
|
134
61
|
if (type !== void 0) result.type = type;
|
|
135
62
|
if (typeof c.install === "string") result.install = c.install;
|
|
63
|
+
if (Array.isArray(c.registryDependencies)) {
|
|
64
|
+
const deps = asStringArray(c.registryDependencies);
|
|
65
|
+
if (deps.length > 0) result.registryDependencies = deps;
|
|
66
|
+
}
|
|
136
67
|
return result;
|
|
137
68
|
});
|
|
138
69
|
const blocks = Array.isArray(obj.blocks) ? obj.blocks.flatMap((entry) => {
|
|
@@ -365,6 +296,12 @@ async function resolveFromDirectory(ns, fetchFn) {
|
|
|
365
296
|
}
|
|
366
297
|
//#endregion
|
|
367
298
|
//#region src/commands/add.ts
|
|
299
|
+
var add_exports = /* @__PURE__ */ __exportAll({
|
|
300
|
+
LAYOUT_ALIASES: () => LAYOUT_ALIASES,
|
|
301
|
+
add: () => add,
|
|
302
|
+
resolveBareClosure: () => resolveBareClosure,
|
|
303
|
+
resolveTemplateTarget: () => resolveTemplateTarget
|
|
304
|
+
});
|
|
368
305
|
/** Dependencies always present in a cascade project — never auto-installed. */
|
|
369
306
|
const ASSUMED_DEPS = new Set(["react", "react-dom"]);
|
|
370
307
|
async function fetchText$1(url) {
|
|
@@ -372,6 +309,59 @@ async function fetchText$1(url) {
|
|
|
372
309
|
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
373
310
|
return res.text();
|
|
374
311
|
}
|
|
312
|
+
/** Resolve where a template's own file is written — relative to the project root. */
|
|
313
|
+
function resolveTemplateTarget(cwd, target) {
|
|
314
|
+
return resolve(cwd, target);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Names from other libraries → the cascivo layout primitive that fills the same
|
|
318
|
+
* role, so `cascivo add flex` installs `layout/stack`. Bare names that already
|
|
319
|
+
* resolve (e.g. `stack` → `layout/stack` via suffix match) need no alias.
|
|
320
|
+
*/
|
|
321
|
+
const LAYOUT_ALIASES = {
|
|
322
|
+
flex: "stack",
|
|
323
|
+
box: "stack",
|
|
324
|
+
hstack: "stack",
|
|
325
|
+
vstack: "stack",
|
|
326
|
+
gap: "spacer"
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Resolve the transitive closure of bare-name registry entries, following each
|
|
330
|
+
* entry's `registryDependencies`. De-duped by resolved registry name and
|
|
331
|
+
* cycle-safe (an entry is processed at most once). Returns resolved entries in
|
|
332
|
+
* install order plus the names that could not be found. Pure — no I/O.
|
|
333
|
+
*/
|
|
334
|
+
function resolveBareClosure(registry, bareSpecs) {
|
|
335
|
+
const queue = bareSpecs.map((name) => ({
|
|
336
|
+
name,
|
|
337
|
+
requested: true
|
|
338
|
+
}));
|
|
339
|
+
const installed = /* @__PURE__ */ new Set();
|
|
340
|
+
const resolved = [];
|
|
341
|
+
const missing = [];
|
|
342
|
+
while (queue.length > 0) {
|
|
343
|
+
const { name, requested } = queue.shift();
|
|
344
|
+
const entry = findComponent(registry, LAYOUT_ALIASES[name.toLowerCase()] ?? name);
|
|
345
|
+
if (!entry) {
|
|
346
|
+
missing.push(name);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (installed.has(entry.name)) continue;
|
|
350
|
+
installed.add(entry.name);
|
|
351
|
+
resolved.push({
|
|
352
|
+
entry,
|
|
353
|
+
requested
|
|
354
|
+
});
|
|
355
|
+
for (const dep of entry.registryDependencies ?? []) queue.push({
|
|
356
|
+
name: dep,
|
|
357
|
+
requested: false
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
resolved,
|
|
362
|
+
missing
|
|
363
|
+
};
|
|
364
|
+
}
|
|
375
365
|
function isMultiRegistrySpec(name) {
|
|
376
366
|
return name.startsWith("@") || name.startsWith("http://") || name.startsWith("https://") || name.startsWith("./") || name.startsWith("/") || name.split("/").length >= 3;
|
|
377
367
|
}
|
|
@@ -380,41 +370,51 @@ async function add(names, config, opts = {}) {
|
|
|
380
370
|
console.error("Usage: cascivo add <component...>");
|
|
381
371
|
return;
|
|
382
372
|
}
|
|
373
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
383
374
|
const multiSpecs = names.filter(isMultiRegistrySpec);
|
|
384
375
|
const bareSpecs = names.filter((n) => !isMultiRegistrySpec(n));
|
|
385
|
-
let lock = await readLock() ?? createLock();
|
|
376
|
+
let lock = await readLock(cwd) ?? createLock();
|
|
386
377
|
if (multiSpecs.length > 0) {
|
|
387
378
|
const plan = await resolveClosure(multiSpecs, config, process.env, resolveFromDirectory);
|
|
388
379
|
if (opts.dryRun) {
|
|
389
380
|
console.log("Dry run — would install:");
|
|
390
381
|
for (const item of plan.items) {
|
|
391
|
-
|
|
392
|
-
|
|
382
|
+
const kind = isTemplateItem(item.item) ? " [template]" : "";
|
|
383
|
+
console.log(` ${item.spec} (${item.item.name}@${item.item.version})${kind} from ${item.itemUrl}`);
|
|
384
|
+
for (const f of item.item.files) {
|
|
385
|
+
const arrow = isTemplateItem(item.item) && f.target ? ` → ${f.target}` : "";
|
|
386
|
+
console.log(` ${f.url}${arrow}`);
|
|
387
|
+
}
|
|
388
|
+
if (item.item.registryDependencies?.length) console.log(` components: ${item.item.registryDependencies.join(", ")}`);
|
|
393
389
|
}
|
|
394
390
|
return;
|
|
395
391
|
}
|
|
396
392
|
const isThirdParty = (itemUrl) => !itemUrl.includes("cascivo.com");
|
|
397
393
|
for (const { item, itemUrl, registryBase } of plan.items) {
|
|
398
394
|
if (isThirdParty(itemUrl)) console.log(`\nReview before use — files from non-directory registry: ${registryBase}\n` + (item.files ?? []).map((f) => ` ${f.url}`).join("\n"));
|
|
399
|
-
await installItem(item, config,
|
|
395
|
+
const fileHashes = await installItem(item, config, cwd);
|
|
400
396
|
lock = updateLockEntry(lock, item.name, {
|
|
401
397
|
registry: registryBase,
|
|
402
398
|
version: item.version,
|
|
403
399
|
installedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
404
|
-
files:
|
|
400
|
+
files: fileHashes
|
|
405
401
|
});
|
|
406
402
|
}
|
|
407
|
-
await writeLock(lock);
|
|
403
|
+
await writeLock(lock, cwd);
|
|
408
404
|
return;
|
|
409
405
|
}
|
|
410
406
|
const registry = await fetchRegistry(config.registry);
|
|
411
407
|
const missingDeps = /* @__PURE__ */ new Set();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
408
|
+
const registryBase = config.registry.replace(/\/registry\.json$/, "").replace(/\/r$/, "");
|
|
409
|
+
for (const spec of bareSpecs) {
|
|
410
|
+
const alias = LAYOUT_ALIASES[spec.toLowerCase()];
|
|
411
|
+
if (!alias) continue;
|
|
412
|
+
const target = findComponent(registry, alias);
|
|
413
|
+
if (target) console.log(`"${spec}" is not a cascivo component — installing ${target.name} (its ${spec} primitive).`);
|
|
414
|
+
}
|
|
415
|
+
const { resolved, missing } = resolveBareClosure(registry, bareSpecs);
|
|
416
|
+
for (const name of missing) console.error(`Component "${name}" not found in registry. Run "cascivo list".`);
|
|
417
|
+
for (const { entry, requested } of resolved) {
|
|
418
418
|
if (entry.type === "chart") {
|
|
419
419
|
const pkg = entry.install ?? "@cascivo/charts";
|
|
420
420
|
console.log(`Chart "${entry.name}" is distributed as an npm package.`);
|
|
@@ -426,35 +426,446 @@ async function add(names, config, opts = {}) {
|
|
|
426
426
|
const fileHashes = {};
|
|
427
427
|
for (const url of entry.files) {
|
|
428
428
|
const content = await fetchText$1(url);
|
|
429
|
-
const dest = resolveOutputPath(config.outputDir, outputName, fileName(url));
|
|
429
|
+
const dest = resolveOutputPath(config.outputDir, outputName, fileName(url), cwd);
|
|
430
430
|
await writeFileSafe(dest, content);
|
|
431
431
|
fileHashes[dest] = sha256(content);
|
|
432
432
|
}
|
|
433
433
|
for (const dep of entry.dependencies) if (!ASSUMED_DEPS.has(dep)) missingDeps.add(dep);
|
|
434
|
-
const registryBase = config.registry.replace(/\/registry\.json$/, "").replace(/\/r$/, "");
|
|
435
434
|
lock = updateLockEntry(lock, entry.name, {
|
|
436
435
|
registry: registryBase,
|
|
437
436
|
version: entry.version,
|
|
438
437
|
installedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
439
438
|
files: fileHashes
|
|
440
439
|
});
|
|
441
|
-
|
|
440
|
+
const suffix = requested ? "" : " (dependency)";
|
|
441
|
+
console.log(`Added ${entry.name} to ${config.outputDir}/${outputName}/${suffix}`);
|
|
442
442
|
}
|
|
443
443
|
if (missingDeps.size > 0) installPackages([...missingDeps]);
|
|
444
|
-
await writeLock(lock);
|
|
444
|
+
await writeLock(lock, cwd);
|
|
445
445
|
}
|
|
446
|
-
|
|
447
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Install a single resolved item, returning a map of written path → content
|
|
448
|
+
* hash for the lockfile. A template writes its own files to their `target`
|
|
449
|
+
* paths (relative to the project root); every other item writes its files under
|
|
450
|
+
* the components output directory. A template's components are installed
|
|
451
|
+
* separately as their own plan entries (via `registryDependencies`).
|
|
452
|
+
*/
|
|
453
|
+
async function installItem(item, config, cwd) {
|
|
454
|
+
const fileHashes = {};
|
|
448
455
|
const missingDeps = /* @__PURE__ */ new Set();
|
|
456
|
+
const template = isTemplateItem(item);
|
|
457
|
+
const outputName = item.name.includes("/") ? item.name.split("/").pop() : item.name;
|
|
449
458
|
for (const file of item.files ?? []) try {
|
|
450
459
|
const content = await fetchText$1(file.url);
|
|
451
|
-
|
|
460
|
+
const dest = template && file.target ? resolveTemplateTarget(cwd, file.target) : resolveOutputPath(config.outputDir, outputName, fileName(file.url), cwd);
|
|
461
|
+
await writeFileSafe(dest, content);
|
|
462
|
+
fileHashes[dest] = sha256(content);
|
|
452
463
|
} catch {
|
|
453
464
|
console.warn(` Could not fetch ${file.url}`);
|
|
454
465
|
}
|
|
455
466
|
for (const dep of item.dependencies ?? []) if (!ASSUMED_DEPS.has(dep)) missingDeps.add(dep);
|
|
456
467
|
if (missingDeps.size > 0) installPackages([...missingDeps]);
|
|
457
|
-
|
|
468
|
+
if (template) {
|
|
469
|
+
const pages = item.files?.filter((f) => f.target).map((f) => f.target) ?? [];
|
|
470
|
+
console.log(`Installed template ${item.name} (${pages.length} files)`);
|
|
471
|
+
} else console.log(`Added ${item.name} to ${config.outputDir}/${outputName}/`);
|
|
472
|
+
return fileHashes;
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/commands/create.ts
|
|
476
|
+
const THEMES$2 = [
|
|
477
|
+
"light",
|
|
478
|
+
"dark",
|
|
479
|
+
"warm"
|
|
480
|
+
];
|
|
481
|
+
/** Version specifier used for every `@cascivo/*` dependency in generated apps. */
|
|
482
|
+
const CASCIVO_DEP = "latest";
|
|
483
|
+
/** Slug suitable for a union-member string literal: lower-kebab, alnum only. */
|
|
484
|
+
function slug(label) {
|
|
485
|
+
return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "section";
|
|
486
|
+
}
|
|
487
|
+
/** PascalCase identifier derived from a free-text label. */
|
|
488
|
+
function pascalCase(label) {
|
|
489
|
+
const safe = label.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("") || "Section";
|
|
490
|
+
return /^[0-9]/.test(safe) ? `Section${safe}` : safe;
|
|
491
|
+
}
|
|
492
|
+
/** Normalize the project name into a valid npm package name. */
|
|
493
|
+
function packageName(name) {
|
|
494
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "cascivo-app";
|
|
495
|
+
}
|
|
496
|
+
/** Turn raw section labels into unique keyed/component-named sections. */
|
|
497
|
+
function resolveSections(labels) {
|
|
498
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
499
|
+
const seenComponents = /* @__PURE__ */ new Set();
|
|
500
|
+
const sections = [];
|
|
501
|
+
for (const label of labels) {
|
|
502
|
+
const trimmed = label.trim();
|
|
503
|
+
if (!trimmed) continue;
|
|
504
|
+
let key = slug(trimmed);
|
|
505
|
+
let component = pascalCase(trimmed);
|
|
506
|
+
let n = 2;
|
|
507
|
+
while (seenKeys.has(key) || seenComponents.has(component)) {
|
|
508
|
+
key = `${slug(trimmed)}-${n}`;
|
|
509
|
+
component = `${pascalCase(trimmed)}${n}`;
|
|
510
|
+
n++;
|
|
511
|
+
}
|
|
512
|
+
seenKeys.add(key);
|
|
513
|
+
seenComponents.add(component);
|
|
514
|
+
sections.push({
|
|
515
|
+
key,
|
|
516
|
+
label: trimmed,
|
|
517
|
+
component
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return sections.length > 0 ? sections : [{
|
|
521
|
+
key: "home",
|
|
522
|
+
label: "Home",
|
|
523
|
+
component: "Home"
|
|
524
|
+
}];
|
|
525
|
+
}
|
|
526
|
+
function packageJson(opts) {
|
|
527
|
+
const pkg = {
|
|
528
|
+
name: packageName(opts.name),
|
|
529
|
+
private: true,
|
|
530
|
+
version: "0.0.0",
|
|
531
|
+
type: "module",
|
|
532
|
+
scripts: {
|
|
533
|
+
dev: "vite",
|
|
534
|
+
build: "tsc && vite build",
|
|
535
|
+
preview: "vite preview"
|
|
536
|
+
},
|
|
537
|
+
dependencies: {
|
|
538
|
+
"@cascivo/core": CASCIVO_DEP,
|
|
539
|
+
"@cascivo/react": CASCIVO_DEP,
|
|
540
|
+
"@cascivo/themes": CASCIVO_DEP,
|
|
541
|
+
"@cascivo/tokens": CASCIVO_DEP,
|
|
542
|
+
react: "^19.0.0",
|
|
543
|
+
"react-dom": "^19.0.0"
|
|
544
|
+
},
|
|
545
|
+
devDependencies: {
|
|
546
|
+
"@types/react": "^19.0.0",
|
|
547
|
+
"@types/react-dom": "^19.0.0",
|
|
548
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
549
|
+
typescript: "^5.7.0",
|
|
550
|
+
vite: "^7.0.0"
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
554
|
+
}
|
|
555
|
+
function tsconfig() {
|
|
556
|
+
return JSON.stringify({
|
|
557
|
+
compilerOptions: {
|
|
558
|
+
target: "ES2022",
|
|
559
|
+
useDefineForClassFields: true,
|
|
560
|
+
lib: [
|
|
561
|
+
"ES2022",
|
|
562
|
+
"DOM",
|
|
563
|
+
"DOM.Iterable"
|
|
564
|
+
],
|
|
565
|
+
module: "ESNext",
|
|
566
|
+
skipLibCheck: true,
|
|
567
|
+
moduleResolution: "bundler",
|
|
568
|
+
allowImportingTsExtensions: true,
|
|
569
|
+
resolveJsonModule: true,
|
|
570
|
+
isolatedModules: true,
|
|
571
|
+
moduleDetection: "force",
|
|
572
|
+
noEmit: true,
|
|
573
|
+
jsx: "react-jsx",
|
|
574
|
+
strict: true,
|
|
575
|
+
noUnusedLocals: true,
|
|
576
|
+
noUnusedParameters: true,
|
|
577
|
+
noFallthroughCasesInSwitch: true
|
|
578
|
+
},
|
|
579
|
+
include: ["src"]
|
|
580
|
+
}, null, 2) + "\n";
|
|
581
|
+
}
|
|
582
|
+
function viteConfig() {
|
|
583
|
+
return `import react from '@vitejs/plugin-react'
|
|
584
|
+
import { defineConfig } from 'vite'
|
|
585
|
+
|
|
586
|
+
export default defineConfig({
|
|
587
|
+
plugins: [react()],
|
|
588
|
+
})
|
|
589
|
+
`;
|
|
590
|
+
}
|
|
591
|
+
function indexHtml(opts) {
|
|
592
|
+
return `<!doctype html>
|
|
593
|
+
<html lang="en" data-theme="${opts.theme}">
|
|
594
|
+
<head>
|
|
595
|
+
<meta charset="UTF-8" />
|
|
596
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
597
|
+
<title>${opts.name}</title>
|
|
598
|
+
<style>
|
|
599
|
+
@layer cascivo.reset, cascivo.tokens, cascivo.component, cascivo.theme;
|
|
600
|
+
@layer cascivo.reset {
|
|
601
|
+
*,
|
|
602
|
+
*::before,
|
|
603
|
+
*::after {
|
|
604
|
+
box-sizing: border-box;
|
|
605
|
+
margin: 0;
|
|
606
|
+
padding: 0;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
html,
|
|
610
|
+
body,
|
|
611
|
+
#root {
|
|
612
|
+
height: 100%;
|
|
613
|
+
}
|
|
614
|
+
</style>
|
|
615
|
+
</head>
|
|
616
|
+
<body>
|
|
617
|
+
<div id="root"></div>
|
|
618
|
+
<script type="module" src="/src/main.tsx"><\/script>
|
|
619
|
+
</body>
|
|
620
|
+
</html>
|
|
621
|
+
`;
|
|
622
|
+
}
|
|
623
|
+
function mainTsx() {
|
|
624
|
+
return `import React from 'react'
|
|
625
|
+
import ReactDOM from 'react-dom/client'
|
|
626
|
+
import App from './App'
|
|
627
|
+
|
|
628
|
+
const root = document.getElementById('root')
|
|
629
|
+
if (root) {
|
|
630
|
+
ReactDOM.createRoot(root).render(
|
|
631
|
+
<React.StrictMode>
|
|
632
|
+
<App />
|
|
633
|
+
</React.StrictMode>,
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
`;
|
|
637
|
+
}
|
|
638
|
+
function viteEnv() {
|
|
639
|
+
return `/// <reference types="vite/client" />\n`;
|
|
640
|
+
}
|
|
641
|
+
function appTsx(opts, sections) {
|
|
642
|
+
const sectionImports = sections.map((s) => `import { ${s.component} } from './sections/${s.component}'`).join("\n");
|
|
643
|
+
const unionType = sections.map((s) => `'${s.key}'`).join(" | ");
|
|
644
|
+
const navItems = sections.map((s) => ` {
|
|
645
|
+
label: '${s.label.replace(/'/g, "\\'")}',
|
|
646
|
+
active: section.value === '${s.key}',
|
|
647
|
+
onClick: (e) => {
|
|
648
|
+
e.preventDefault()
|
|
649
|
+
section.value = '${s.key}'
|
|
650
|
+
},
|
|
651
|
+
},`).join("\n");
|
|
652
|
+
const renderedSections = sections.map((s) => ` {section.value === '${s.key}' && <${s.component} />}`).join("\n");
|
|
653
|
+
return `'use client'
|
|
654
|
+
import { signal, useSignals } from '@cascivo/core'
|
|
655
|
+
import { AppShell, ShellHeader, SideNav, type SideNavItem } from '@cascivo/react'
|
|
656
|
+
${sectionImports}
|
|
657
|
+
|
|
658
|
+
import '@cascivo/tokens'
|
|
659
|
+
import '@cascivo/themes/${opts.theme}.css'
|
|
660
|
+
import '@cascivo/react/styles.css'
|
|
661
|
+
|
|
662
|
+
type Section = ${unionType}
|
|
663
|
+
|
|
664
|
+
const section = signal<Section>('${sections[0].key}')
|
|
665
|
+
|
|
666
|
+
export default function App() {
|
|
667
|
+
useSignals()
|
|
668
|
+
|
|
669
|
+
const navItems: SideNavItem[] = [
|
|
670
|
+
${navItems}
|
|
671
|
+
]
|
|
672
|
+
|
|
673
|
+
return (
|
|
674
|
+
<AppShell
|
|
675
|
+
header={<ShellHeader brand={{ name: '${opts.name.replace(/'/g, "\\'")}' }} />}
|
|
676
|
+
nav={<SideNav items={navItems} />}
|
|
677
|
+
>
|
|
678
|
+
${renderedSections}
|
|
679
|
+
</AppShell>
|
|
680
|
+
)
|
|
681
|
+
}
|
|
682
|
+
`;
|
|
683
|
+
}
|
|
684
|
+
function sectionTsx(section) {
|
|
685
|
+
return `import { Card, CardContent, CardHeader, CardTitle, Heading, Text } from '@cascivo/react'
|
|
686
|
+
|
|
687
|
+
export function ${section.component}() {
|
|
688
|
+
return (
|
|
689
|
+
<div
|
|
690
|
+
style={{
|
|
691
|
+
display: 'grid',
|
|
692
|
+
gap: 'var(--cascivo-space-6)',
|
|
693
|
+
padding: 'var(--cascivo-space-6)',
|
|
694
|
+
maxWidth: '64rem',
|
|
695
|
+
}}
|
|
696
|
+
>
|
|
697
|
+
<div style={{ display: 'grid', gap: 'var(--cascivo-space-2)' }}>
|
|
698
|
+
<Heading level={1}>${section.label}</Heading>
|
|
699
|
+
<Text muted>
|
|
700
|
+
Edit <code>src/sections/${section.component}.tsx</code> to build out this page.
|
|
701
|
+
</Text>
|
|
702
|
+
</div>
|
|
703
|
+
|
|
704
|
+
<Card>
|
|
705
|
+
<CardHeader>
|
|
706
|
+
<CardTitle>Get started</CardTitle>
|
|
707
|
+
</CardHeader>
|
|
708
|
+
<CardContent>
|
|
709
|
+
<Text>
|
|
710
|
+
This page is wired into the app shell. Add components with{' '}
|
|
711
|
+
<code>npx cascivo add <component></code>.
|
|
712
|
+
</Text>
|
|
713
|
+
</CardContent>
|
|
714
|
+
</Card>
|
|
715
|
+
</div>
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
`;
|
|
719
|
+
}
|
|
720
|
+
function cascivoConfig(opts) {
|
|
721
|
+
return `import type { CascadeConfig } from 'cascivo'
|
|
722
|
+
|
|
723
|
+
const config: CascadeConfig = {
|
|
724
|
+
registry: 'https://cascivo.com/registry.json',
|
|
725
|
+
outputDir: 'src/components/ui',
|
|
726
|
+
theme: '${opts.theme}',
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export default config
|
|
730
|
+
`;
|
|
731
|
+
}
|
|
732
|
+
function gitignore() {
|
|
733
|
+
return `node_modules
|
|
734
|
+
dist
|
|
735
|
+
*.local
|
|
736
|
+
.DS_Store
|
|
737
|
+
`;
|
|
738
|
+
}
|
|
739
|
+
function readme(opts) {
|
|
740
|
+
return `# ${opts.name}
|
|
741
|
+
|
|
742
|
+
A [cascivo](https://cascivo.com) app — Vite + React + TypeScript, pre-wired with
|
|
743
|
+
the cascivo app shell, side navigation, and the \`${opts.theme}\` theme.
|
|
744
|
+
|
|
745
|
+
## Develop
|
|
746
|
+
|
|
747
|
+
\`\`\`sh
|
|
748
|
+
npm install
|
|
749
|
+
npm run dev
|
|
750
|
+
\`\`\`
|
|
751
|
+
|
|
752
|
+
## Structure
|
|
753
|
+
|
|
754
|
+
- \`src/App.tsx\` — app shell, navigation, and section routing
|
|
755
|
+
- \`src/sections/\` — one component per nav item
|
|
756
|
+
|
|
757
|
+
Add more components with \`npx cascivo add <component>\`.
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
/** Build the full set of files for a new cascivo app. Pure — no filesystem I/O. */
|
|
761
|
+
function buildScaffold(opts) {
|
|
762
|
+
const sections = resolveSections(opts.sections);
|
|
763
|
+
return [
|
|
764
|
+
{
|
|
765
|
+
path: "package.json",
|
|
766
|
+
contents: packageJson(opts)
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
path: "tsconfig.json",
|
|
770
|
+
contents: tsconfig()
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
path: "vite.config.ts",
|
|
774
|
+
contents: viteConfig()
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
path: "index.html",
|
|
778
|
+
contents: indexHtml(opts)
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
path: "cascivo.config.ts",
|
|
782
|
+
contents: cascivoConfig(opts)
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
path: ".gitignore",
|
|
786
|
+
contents: gitignore()
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
path: "README.md",
|
|
790
|
+
contents: readme(opts)
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
path: "src/main.tsx",
|
|
794
|
+
contents: mainTsx()
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
path: "src/vite-env.d.ts",
|
|
798
|
+
contents: viteEnv()
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
path: "src/App.tsx",
|
|
802
|
+
contents: appTsx(opts, sections)
|
|
803
|
+
},
|
|
804
|
+
...sections.map((s) => ({
|
|
805
|
+
path: `src/sections/${s.component}.tsx`,
|
|
806
|
+
contents: sectionTsx(s)
|
|
807
|
+
}))
|
|
808
|
+
];
|
|
809
|
+
}
|
|
810
|
+
function flagValue(args, key) {
|
|
811
|
+
const eq = args.find((a) => a.startsWith(`--${key}=`));
|
|
812
|
+
if (eq) return eq.slice(key.length + 3);
|
|
813
|
+
const idx = args.indexOf(`--${key}`);
|
|
814
|
+
const next = idx !== -1 ? args[idx + 1] : void 0;
|
|
815
|
+
return next && !next.startsWith("--") ? next : void 0;
|
|
816
|
+
}
|
|
817
|
+
const DEFAULT_SECTIONS = [
|
|
818
|
+
"Dashboard",
|
|
819
|
+
"Reports",
|
|
820
|
+
"Settings"
|
|
821
|
+
];
|
|
822
|
+
async function create(args, cwd = process.cwd()) {
|
|
823
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
824
|
+
const nameArg = args.find((a) => !a.startsWith("-"));
|
|
825
|
+
const themeArg = flagValue(args, "theme");
|
|
826
|
+
const sectionsArg = flagValue(args, "sections");
|
|
827
|
+
const rl = !yes && stdin.isTTY ? createInterface({
|
|
828
|
+
input: stdin,
|
|
829
|
+
output: stdout
|
|
830
|
+
}) : null;
|
|
831
|
+
try {
|
|
832
|
+
let name = nameArg;
|
|
833
|
+
if (!name && rl) name = (await rl.question("Project name? [my-cascivo-app]: ")).trim();
|
|
834
|
+
name = name || "my-cascivo-app";
|
|
835
|
+
let theme = (themeArg ?? "").toLowerCase();
|
|
836
|
+
if (!THEMES$2.includes(theme) && rl) theme = (await rl.question("Theme? (light/dark/warm) [light]: ")).trim().toLowerCase();
|
|
837
|
+
const resolvedTheme = THEMES$2.includes(theme) ? theme : "light";
|
|
838
|
+
let sectionsInput = sectionsArg;
|
|
839
|
+
if (!sectionsInput && rl) sectionsInput = (await rl.question(`Nav sections? (comma-separated) [${DEFAULT_SECTIONS.join(", ")}]: `)).trim();
|
|
840
|
+
const sections = (sectionsInput ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
841
|
+
const opts = {
|
|
842
|
+
name,
|
|
843
|
+
theme: resolvedTheme,
|
|
844
|
+
sections: sections.length > 0 ? sections : DEFAULT_SECTIONS
|
|
845
|
+
};
|
|
846
|
+
const targetDir = join(cwd, name);
|
|
847
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
848
|
+
console.error(`Target directory "${name}" already exists and is not empty.`);
|
|
849
|
+
process.exitCode = 1;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const files = buildScaffold(opts);
|
|
853
|
+
for (const file of files) await writeFileSafe(join(targetDir, file.path), file.contents);
|
|
854
|
+
console.log(`\nCreated ${name} with the ${resolvedTheme} theme (${files.length} files).`);
|
|
855
|
+
const templateSpec = flagValue(args, "template");
|
|
856
|
+
if (templateSpec) {
|
|
857
|
+
const { add } = await Promise.resolve().then(() => add_exports);
|
|
858
|
+
const { loadConfig } = await import("./config-CsnCR5eh.mjs").then((n) => n.n);
|
|
859
|
+
console.log(`\nInstalling template "${templateSpec}"…`);
|
|
860
|
+
await add([templateSpec], await loadConfig(), { cwd: targetDir });
|
|
861
|
+
}
|
|
862
|
+
console.log("\nNext steps:");
|
|
863
|
+
console.log(` cd ${name}`);
|
|
864
|
+
console.log(" npm install");
|
|
865
|
+
console.log(" npm run dev");
|
|
866
|
+
} finally {
|
|
867
|
+
rl?.close();
|
|
868
|
+
}
|
|
458
869
|
}
|
|
459
870
|
//#endregion
|
|
460
871
|
//#region src/commands/doctor.ts
|
|
@@ -716,9 +1127,30 @@ async function list(config, options = {}) {
|
|
|
716
1127
|
return existsSync(join(process.cwd(), config.outputDir, outputName));
|
|
717
1128
|
});
|
|
718
1129
|
console.log(formatList(components));
|
|
1130
|
+
console.log("\nTip: install any entry by its bare name — `cascivo add stack` resolves to `layout/stack`. Layout primitives (Stack, Grid, AutoGrid, Columns, Spacer, Center) replace inline-style layout; see docs/cookbooks/layout-and-spacing.md.");
|
|
719
1131
|
}
|
|
720
1132
|
//#endregion
|
|
721
1133
|
//#region src/commands/registry.ts
|
|
1134
|
+
/**
|
|
1135
|
+
* Validate every template item in an index: each must pass `validateTemplate`,
|
|
1136
|
+
* and any bare `registryDependencies` not present in the same index produce a
|
|
1137
|
+
* warning (they are expected to resolve from another registry at install time).
|
|
1138
|
+
* Returns `{ errors, warnings }`.
|
|
1139
|
+
*/
|
|
1140
|
+
function validateTemplates(index) {
|
|
1141
|
+
const errors = [];
|
|
1142
|
+
const warnings = [];
|
|
1143
|
+
const localNames = new Set(index.items.map((i) => i.name));
|
|
1144
|
+
for (const item of index.items) {
|
|
1145
|
+
if (!isTemplateItem(item)) continue;
|
|
1146
|
+
for (const e of validateTemplate(item)) errors.push(`template "${item.name}": ${e}`);
|
|
1147
|
+
for (const dep of item.registryDependencies ?? []) if (!(dep.includes("/") || dep.startsWith("@") || dep.startsWith("http")) && !localNames.has(dep)) warnings.push(`template "${item.name}": component "${dep}" is not in this index — it must resolve from another registry at install time`);
|
|
1148
|
+
}
|
|
1149
|
+
return {
|
|
1150
|
+
errors,
|
|
1151
|
+
warnings
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
722
1154
|
async function registryBuild(args) {
|
|
723
1155
|
let inFile = "cascade-registry.json";
|
|
724
1156
|
let outDir = "public/r";
|
|
@@ -748,6 +1180,13 @@ async function registryBuild(args) {
|
|
|
748
1180
|
process.exitCode = 1;
|
|
749
1181
|
return;
|
|
750
1182
|
}
|
|
1183
|
+
const templateResult = validateTemplates(index);
|
|
1184
|
+
for (const w of templateResult.warnings) console.warn(`warn: ${w}`);
|
|
1185
|
+
if (templateResult.errors.length > 0) {
|
|
1186
|
+
for (const e of templateResult.errors) console.error(`error: ${e}`);
|
|
1187
|
+
process.exitCode = 1;
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
751
1190
|
const outPath = resolve(outDir);
|
|
752
1191
|
await buildRegistry(index, outPath);
|
|
753
1192
|
console.log(`cascade registry build: wrote static output to ${outPath}`);
|
|
@@ -1220,6 +1659,40 @@ async function updateCheck(config) {
|
|
|
1220
1659
|
}
|
|
1221
1660
|
//#endregion
|
|
1222
1661
|
//#region src/commands/view.ts
|
|
1662
|
+
/** Render an item's details as text. Pure, so it can be unit-tested. */
|
|
1663
|
+
function formatItem(item) {
|
|
1664
|
+
const lines = [];
|
|
1665
|
+
lines.push(`\n${item.name} v${item.version}`);
|
|
1666
|
+
lines.push(`Type: ${item.type}${item.category ? ` / ${item.category}` : ""}`);
|
|
1667
|
+
lines.push(item.description);
|
|
1668
|
+
if (item.author) lines.push(`Author: ${item.author}`);
|
|
1669
|
+
if (item.license) lines.push(`License: ${item.license}`);
|
|
1670
|
+
if (item.homepage) lines.push(`Homepage: ${item.homepage}`);
|
|
1671
|
+
if (isTemplateItem(item)) try {
|
|
1672
|
+
const meta = asTemplateMeta(item.meta);
|
|
1673
|
+
lines.push(`\nTemplate: ${meta.intent}`);
|
|
1674
|
+
lines.push(`Framework: ${meta.framework}`);
|
|
1675
|
+
if (meta.demoUrl) lines.push(`Demo: ${meta.demoUrl}`);
|
|
1676
|
+
if (meta.pages?.length) {
|
|
1677
|
+
lines.push("Pages:");
|
|
1678
|
+
for (const p of meta.pages) lines.push(` ${p.name} → ${p.target}`);
|
|
1679
|
+
}
|
|
1680
|
+
} catch {}
|
|
1681
|
+
if (item.changelog?.length) {
|
|
1682
|
+
lines.push("\nChangelog:");
|
|
1683
|
+
for (const c of item.changelog.slice(0, 5)) lines.push(` ${c.version}: ${c.note}`);
|
|
1684
|
+
}
|
|
1685
|
+
if (item.files?.length) {
|
|
1686
|
+
lines.push("\nFiles:");
|
|
1687
|
+
for (const f of item.files) lines.push(` ${f.url}${f.target ? ` → ${f.target}` : ""}`);
|
|
1688
|
+
}
|
|
1689
|
+
if (item.dependencies?.length) lines.push(`\nDependencies: ${item.dependencies.join(", ")}`);
|
|
1690
|
+
if (item.registryDependencies?.length) {
|
|
1691
|
+
const label = isTemplateItem(item) ? "Components" : "Registry dependencies";
|
|
1692
|
+
lines.push(`${label}: ${item.registryDependencies.join(", ")}`);
|
|
1693
|
+
}
|
|
1694
|
+
return lines.join("\n");
|
|
1695
|
+
}
|
|
1223
1696
|
async function view(args, config) {
|
|
1224
1697
|
const spec = args[0];
|
|
1225
1698
|
if (!spec) {
|
|
@@ -1231,25 +1704,7 @@ async function view(args, config) {
|
|
|
1231
1704
|
if (headers) fetchOpts.headers = headers;
|
|
1232
1705
|
if (params) fetchOpts.params = params;
|
|
1233
1706
|
const item = await fetchJson(url, fetchOpts);
|
|
1234
|
-
console.log(
|
|
1235
|
-
console.log(`Type: ${item.type}${item.category ? ` / ${item.category}` : ""}`);
|
|
1236
|
-
console.log(`${item.description}`);
|
|
1237
|
-
if (item.author) console.log(`Author: ${item.author}`);
|
|
1238
|
-
if (item.license) console.log(`License: ${item.license}`);
|
|
1239
|
-
if (item.homepage) console.log(`Homepage: ${item.homepage}`);
|
|
1240
|
-
if (item.changelog?.length) {
|
|
1241
|
-
console.log("\nChangelog:");
|
|
1242
|
-
for (const c of item.changelog.slice(0, 5)) console.log(` ${c.version}: ${c.note}`);
|
|
1243
|
-
}
|
|
1244
|
-
if (item.files?.length) {
|
|
1245
|
-
console.log("\nFiles:");
|
|
1246
|
-
for (const f of item.files) {
|
|
1247
|
-
const target = f.target ? ` → ${f.target}` : "";
|
|
1248
|
-
console.log(` ${f.url}${target}`);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
if (item.dependencies?.length) console.log(`\nDependencies: ${item.dependencies.join(", ")}`);
|
|
1252
|
-
if (item.registryDependencies?.length) console.log(`Registry dependencies: ${item.registryDependencies.join(", ")}`);
|
|
1707
|
+
console.log(formatItem(item));
|
|
1253
1708
|
}
|
|
1254
1709
|
//#endregion
|
|
1255
1710
|
//#region src/index.ts
|
|
@@ -1259,8 +1714,10 @@ const HELP = `cascivo ${VERSION} — the CSS-native, signal-driven, AI-first des
|
|
|
1259
1714
|
Usage: cascivo <command> [options]
|
|
1260
1715
|
|
|
1261
1716
|
Commands:
|
|
1717
|
+
create [name] Scaffold a new ready-to-run app (shell + nav + theme)
|
|
1718
|
+
(--template <spec>: start from a marketplace template)
|
|
1262
1719
|
init Set up cascivo in the current project
|
|
1263
|
-
add <component...> Add
|
|
1720
|
+
add <component...> Add components or a template to your project
|
|
1264
1721
|
list [--installed] List available components
|
|
1265
1722
|
update [component] Update installed components (--check: list outdated)
|
|
1266
1723
|
search <query> Search components across registries
|
|
@@ -1271,11 +1728,15 @@ Commands:
|
|
|
1271
1728
|
doctor [--ci] Check components for rule violations
|
|
1272
1729
|
audit --ai <paths...> Audit AI-generated code against the cascivo contract
|
|
1273
1730
|
registry build Build a static registry from a cascivo-registry.json file
|
|
1731
|
+
template init <name> Scaffold a new template (source + manifest + registry entry)
|
|
1274
1732
|
|
|
1275
1733
|
Run "cascivo <command> --help" for details.`;
|
|
1276
1734
|
async function run(args) {
|
|
1277
1735
|
const [command, ...rest] = args;
|
|
1278
1736
|
switch (command) {
|
|
1737
|
+
case "create":
|
|
1738
|
+
await create(rest);
|
|
1739
|
+
break;
|
|
1279
1740
|
case "init":
|
|
1280
1741
|
await init();
|
|
1281
1742
|
break;
|
|
@@ -1304,7 +1765,7 @@ async function run(args) {
|
|
|
1304
1765
|
await theme(rest);
|
|
1305
1766
|
break;
|
|
1306
1767
|
case "eject": {
|
|
1307
|
-
const { eject } = await import("./eject-
|
|
1768
|
+
const { eject } = await import("./eject-B92gZhLX.mjs");
|
|
1308
1769
|
const flag = (key) => {
|
|
1309
1770
|
const eq = rest.find((r) => r.startsWith(`--${key}=`));
|
|
1310
1771
|
if (eq) return eq.slice(key.length + 3);
|
|
@@ -1331,6 +1792,15 @@ async function run(args) {
|
|
|
1331
1792
|
process.exitCode = 1;
|
|
1332
1793
|
}
|
|
1333
1794
|
break;
|
|
1795
|
+
case "template":
|
|
1796
|
+
if (rest[0] === "init") {
|
|
1797
|
+
const { templateInit } = await import("./template-init-DGEgatij.mjs");
|
|
1798
|
+
await templateInit(rest.slice(1));
|
|
1799
|
+
} else {
|
|
1800
|
+
console.error(`Unknown template subcommand: ${rest[0]}`);
|
|
1801
|
+
process.exitCode = 1;
|
|
1802
|
+
}
|
|
1803
|
+
break;
|
|
1334
1804
|
case "doctor": {
|
|
1335
1805
|
const ci = rest.includes("--ci");
|
|
1336
1806
|
if (rest.includes("--drift")) {
|
|
@@ -1347,7 +1817,7 @@ async function run(args) {
|
|
|
1347
1817
|
break;
|
|
1348
1818
|
}
|
|
1349
1819
|
case "audit": {
|
|
1350
|
-
const { audit } = await import("./audit-
|
|
1820
|
+
const { audit } = await import("./audit-BRn4MeXD.mjs");
|
|
1351
1821
|
await audit(rest, await loadConfig());
|
|
1352
1822
|
break;
|
|
1353
1823
|
}
|