cascivo 0.4.2 → 0.5.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 +4 -1
- package/dist/config-CYlTGdmE.mjs +171 -0
- package/dist/config-CYlTGdmE.mjs.map +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +312 -72
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/readme.body.md +3 -0
- package/dist/config-BsyJbMvD.mjs +0 -106
- package/dist/config-BsyJbMvD.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as detectPackageManager, c as
|
|
2
|
+
import { a as detectPackageManager, c as isPackageManager, l as loadConfig, n as DEFAULT_CONFIG$1, o as installCommand, r as THEMES, s as installHint, t as CASCIVO_HOST, u as __exportAll } from "./config-CYlTGdmE.mjs";
|
|
3
3
|
import { n as resolveOutputPath, r as writeFileSafe, t as readFileSafe } from "./fs-m7ZvuBBm.mjs";
|
|
4
4
|
import { r as fetchTextRetry, t as fetchJson } from "./http-CJZ5W0Fa.mjs";
|
|
5
5
|
import { a as updateLockEntry, c as fileName, i as sha256, l as findComponent, n as createLock, o as writeLock, r as readLock, s as fetchRegistry, t as checkPeerVersions } from "./peer-versions-B2bCgko-.mjs";
|
|
@@ -14,12 +14,16 @@ import { asTemplateMeta, buildRegistry, isTemplateItem, parseLegacyRegistry, val
|
|
|
14
14
|
import { createInterface } from "node:readline/promises";
|
|
15
15
|
//#region src/utils/exec.ts
|
|
16
16
|
/**
|
|
17
|
-
* Install npm packages using the detected
|
|
18
|
-
* the user sees install progress.
|
|
17
|
+
* Install npm packages using the detected (or explicitly chosen) package
|
|
18
|
+
* manager, inheriting stdio so the user sees install progress. On failure it
|
|
19
|
+
* prints the exact command to run by hand — the adopter can always recover even
|
|
20
|
+
* when the spawn fails (e.g. npm crashing inside a pnpm workspace). Returns
|
|
21
|
+
* false if the install failed.
|
|
19
22
|
*/
|
|
20
|
-
function installPackages(packages, cwd = process.cwd()) {
|
|
23
|
+
function installPackages(packages, cwd = process.cwd(), opts = {}) {
|
|
21
24
|
if (packages.length === 0) return true;
|
|
22
|
-
const
|
|
25
|
+
const pm = opts.pm ?? detectPackageManager(cwd);
|
|
26
|
+
const [cmd, args] = installCommand(pm, packages, { dev: opts.dev ?? false });
|
|
23
27
|
console.log(`Installing ${packages.join(", ")} with ${cmd}…`);
|
|
24
28
|
if (spawnSync(cmd, args, {
|
|
25
29
|
cwd,
|
|
@@ -27,6 +31,7 @@ function installPackages(packages, cwd = process.cwd()) {
|
|
|
27
31
|
shell: process.platform === "win32"
|
|
28
32
|
}).status !== 0) {
|
|
29
33
|
console.error(`Failed to install: ${packages.join(", ")}`);
|
|
34
|
+
console.error(`Install them yourself with: ${installHint(pm, packages, { dev: opts.dev ?? false })}`);
|
|
30
35
|
return false;
|
|
31
36
|
}
|
|
32
37
|
return true;
|
|
@@ -167,6 +172,7 @@ async function resolveFromDirectory(ns, fetchFn) {
|
|
|
167
172
|
//#region src/commands/add.ts
|
|
168
173
|
var add_exports = /* @__PURE__ */ __exportAll({
|
|
169
174
|
LAYOUT_ALIASES: () => LAYOUT_ALIASES,
|
|
175
|
+
NAME_NOTES: () => NAME_NOTES,
|
|
170
176
|
add: () => add,
|
|
171
177
|
isThirdParty: () => isThirdParty,
|
|
172
178
|
resolveBareClosure: () => resolveBareClosure,
|
|
@@ -174,23 +180,28 @@ var add_exports = /* @__PURE__ */ __exportAll({
|
|
|
174
180
|
});
|
|
175
181
|
/** Dependencies always present in a cascade project — never auto-installed. */
|
|
176
182
|
const ASSUMED_DEPS = new Set(["react", "react-dom"]);
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
* @cascivo/tokens or @cascivo/themes imported somewhere, they render unstyled.
|
|
180
|
-
* Print the wiring once after a successful install if neither is a dependency.
|
|
181
|
-
*/
|
|
182
|
-
function hintThemesIfMissing(cwd) {
|
|
183
|
-
let deps = {};
|
|
183
|
+
/** Merged dependencies + devDependencies from the project's package.json (empty if none). */
|
|
184
|
+
function readProjectDeps(cwd) {
|
|
184
185
|
try {
|
|
185
186
|
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
186
|
-
|
|
187
|
+
return {
|
|
187
188
|
...pkg.dependencies,
|
|
188
189
|
...pkg.devDependencies
|
|
189
190
|
};
|
|
190
|
-
} catch {
|
|
191
|
+
} catch {
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Copied components read `--cascivo-*` custom properties; without
|
|
197
|
+
* @cascivo/tokens or @cascivo/themes imported somewhere, they render unstyled.
|
|
198
|
+
* Print the wiring once after a successful install if neither is a dependency.
|
|
199
|
+
*/
|
|
200
|
+
function hintThemesIfMissing(cwd, pm) {
|
|
201
|
+
const deps = readProjectDeps(cwd);
|
|
191
202
|
if (deps["@cascivo/themes"] !== void 0 || deps["@cascivo/tokens"] !== void 0) return;
|
|
192
203
|
console.log("\nStyling: cascivo components read --cascivo-* tokens, which this project does not import yet.");
|
|
193
|
-
console.log(
|
|
204
|
+
console.log(` ${installHint(pm, ["@cascivo/themes"])}`);
|
|
194
205
|
console.log(" import '@cascivo/themes/all' // once, in your entry file");
|
|
195
206
|
console.log(" <html data-theme=\"light\"> // or any container");
|
|
196
207
|
}
|
|
@@ -211,6 +222,12 @@ const LAYOUT_ALIASES = {
|
|
|
211
222
|
gap: "spacer"
|
|
212
223
|
};
|
|
213
224
|
/**
|
|
225
|
+
* Disambiguation printed when a requested bare name is a real cascivo component
|
|
226
|
+
* whose meaning differs from the same name elsewhere, so an adopter who grabbed
|
|
227
|
+
* it by name alone learns the right primitive. Keyed by lowercased bare name.
|
|
228
|
+
*/
|
|
229
|
+
const NAME_NOTES = { stack: "Note: cascivo's Stack overlaps children into a card-pile (z-axis), not a vertical spacing layout. For vertical spacing use Flex direction=\"vertical\" (cascivo add flex; aliases: vstack, hstack)." };
|
|
230
|
+
/**
|
|
214
231
|
* Resolve the transitive closure of bare-name registry entries, following each
|
|
215
232
|
* entry's `registryDependencies`. De-duped by resolved registry name and
|
|
216
233
|
* cycle-safe (an entry is processed at most once). Returns resolved entries in
|
|
@@ -264,6 +281,7 @@ async function add(names, config, opts = {}) {
|
|
|
264
281
|
return;
|
|
265
282
|
}
|
|
266
283
|
const cwd = opts.cwd ?? process.cwd();
|
|
284
|
+
const pm = opts.pm ?? detectPackageManager(cwd);
|
|
267
285
|
const multiSpecs = names.filter(isMultiRegistrySpec);
|
|
268
286
|
let bareSpecs = names.filter((n) => !isMultiRegistrySpec(n));
|
|
269
287
|
let registry = null;
|
|
@@ -296,7 +314,10 @@ async function add(names, config, opts = {}) {
|
|
|
296
314
|
if (isThirdParty(itemUrl)) console.log(`\nReview before use — files from non-directory registry: ${registryBase}\n` + (item.files ?? []).map((f) => ` ${f.url}`).join("\n"));
|
|
297
315
|
let fileHashes;
|
|
298
316
|
try {
|
|
299
|
-
fileHashes = await installItem(item, config, cwd
|
|
317
|
+
fileHashes = await installItem(item, config, cwd, {
|
|
318
|
+
pm,
|
|
319
|
+
...opts.noInstall ? { noInstall: true } : {}
|
|
320
|
+
});
|
|
300
321
|
} catch (e) {
|
|
301
322
|
console.error(`Failed to install ${item.name}: ${e instanceof Error ? e.message : e}`);
|
|
302
323
|
process.exitCode = 1;
|
|
@@ -311,7 +332,7 @@ async function add(names, config, opts = {}) {
|
|
|
311
332
|
}
|
|
312
333
|
await writeLock(lock, cwd);
|
|
313
334
|
if (bareSpecs.length === 0) {
|
|
314
|
-
hintThemesIfMissing(cwd);
|
|
335
|
+
hintThemesIfMissing(cwd, pm);
|
|
315
336
|
return;
|
|
316
337
|
}
|
|
317
338
|
}
|
|
@@ -320,6 +341,8 @@ async function add(names, config, opts = {}) {
|
|
|
320
341
|
const missingDeps = /* @__PURE__ */ new Set();
|
|
321
342
|
const registryBase = config.registry.replace(/\/registry\.json$/, "").replace(/\/r$/, "");
|
|
322
343
|
for (const spec of bareSpecs) {
|
|
344
|
+
const note = NAME_NOTES[spec.toLowerCase()];
|
|
345
|
+
if (note) console.log(note);
|
|
323
346
|
const alias = LAYOUT_ALIASES[spec.toLowerCase()];
|
|
324
347
|
if (!alias) continue;
|
|
325
348
|
const target = findComponent(registry, alias);
|
|
@@ -329,13 +352,15 @@ async function add(names, config, opts = {}) {
|
|
|
329
352
|
for (const name of missing) console.error(`Component "${name}" not found in registry. Run "cascivo list".`);
|
|
330
353
|
const peerFloors = {};
|
|
331
354
|
for (const { entry } of resolved) for (const [pkg, floor] of Object.entries(entry.peerVersions ?? {})) peerFloors[pkg] = floor;
|
|
355
|
+
const npmPackages = /* @__PURE__ */ new Set();
|
|
356
|
+
const importLines = [];
|
|
332
357
|
for (const { entry, requested } of resolved) {
|
|
333
358
|
if (entry.install) {
|
|
334
359
|
const pkg = entry.install;
|
|
335
|
-
|
|
336
|
-
console.log(`
|
|
337
|
-
|
|
338
|
-
if (entry.styles)
|
|
360
|
+
npmPackages.add(pkg);
|
|
361
|
+
console.log(`"${entry.name}" is distributed as the npm package ${pkg} — a runtime dependency, not copied source (updates come via your package manager).`);
|
|
362
|
+
importLines.push(` import { ${entry.meta.name} } from '${pkg}'`);
|
|
363
|
+
if (entry.styles) importLines.push(` import '${entry.styles}' // once, in your entry file`);
|
|
339
364
|
continue;
|
|
340
365
|
}
|
|
341
366
|
const outputName = entry.name.includes("/") ? entry.name.split("/").pop() : entry.name;
|
|
@@ -366,16 +391,27 @@ async function add(names, config, opts = {}) {
|
|
|
366
391
|
const suffix = requested ? "" : " (dependency)";
|
|
367
392
|
console.log(`Added ${entry.name} to ${config.outputDir}/${outputName}/${suffix}`);
|
|
368
393
|
}
|
|
369
|
-
if (
|
|
394
|
+
if (npmPackages.size > 0) {
|
|
395
|
+
const deps = readProjectDeps(cwd);
|
|
396
|
+
const needed = [...npmPackages].filter((p) => deps[p] === void 0);
|
|
397
|
+
if (opts.dryRun) console.log(`Dry run — would install npm package(s): ${[...npmPackages].join(", ")}`);
|
|
398
|
+
else if (needed.length > 0) {
|
|
399
|
+
if (opts.noInstall) console.log(`\nSkipped install (--no-install). Install with:\n ${installHint(pm, needed)}`);
|
|
400
|
+
else if (!installPackages(needed, cwd, { pm })) process.exitCode = 1;
|
|
401
|
+
}
|
|
402
|
+
if (importLines.length > 0) console.log(`\nImport it:\n${importLines.join("\n")}`);
|
|
403
|
+
}
|
|
404
|
+
if (missingDeps.size > 0 && !opts.dryRun) if (opts.noInstall) console.log(`\nSkipped install (--no-install). Install with:\n ${installHint(pm, [...missingDeps])}`);
|
|
405
|
+
else installPackages([...missingDeps], cwd, { pm });
|
|
370
406
|
if (Object.keys(peerFloors).length > 0) {
|
|
371
407
|
const violations = await checkPeerVersions(cwd, peerFloors);
|
|
372
408
|
for (const v of violations) {
|
|
373
|
-
console.error(`\nWarning: ${v.pkg} ${v.installed ? `${v.installed} is` : "is not"} installed, but the copied component source needs ${v.pkg} ${v.required}. Run:
|
|
409
|
+
console.error(`\nWarning: ${v.pkg} ${v.installed ? `${v.installed} is` : "is not"} installed, but the copied component source needs ${v.pkg} ${v.required}. Run: ${installHint(pm, [`${v.pkg}@latest`])}`);
|
|
374
410
|
process.exitCode = 1;
|
|
375
411
|
}
|
|
376
412
|
}
|
|
377
413
|
await writeLock(lock, cwd);
|
|
378
|
-
hintThemesIfMissing(cwd);
|
|
414
|
+
hintThemesIfMissing(cwd, pm);
|
|
379
415
|
}
|
|
380
416
|
/**
|
|
381
417
|
* Install a single resolved item, returning a map of written path → content
|
|
@@ -384,7 +420,7 @@ async function add(names, config, opts = {}) {
|
|
|
384
420
|
* the components output directory. A template's components are installed
|
|
385
421
|
* separately as their own plan entries (via `registryDependencies`).
|
|
386
422
|
*/
|
|
387
|
-
async function installItem(item, config, cwd) {
|
|
423
|
+
async function installItem(item, config, cwd, opts = {}) {
|
|
388
424
|
const fileHashes = {};
|
|
389
425
|
const missingDeps = /* @__PURE__ */ new Set();
|
|
390
426
|
const template = isTemplateItem(item);
|
|
@@ -399,7 +435,8 @@ async function installItem(item, config, cwd) {
|
|
|
399
435
|
fileHashes[dest] = sha256(content);
|
|
400
436
|
}
|
|
401
437
|
for (const dep of item.dependencies ?? []) if (!ASSUMED_DEPS.has(dep)) missingDeps.add(dep);
|
|
402
|
-
if (missingDeps.size > 0)
|
|
438
|
+
if (missingDeps.size > 0) if (opts.noInstall) console.log(`\nSkipped install (--no-install). Install with:\n ${installHint(opts.pm ?? detectPackageManager(cwd), [...missingDeps])}`);
|
|
439
|
+
else installPackages([...missingDeps], cwd, opts.pm ? { pm: opts.pm } : {});
|
|
403
440
|
if (template) {
|
|
404
441
|
const pages = item.files?.filter((f) => f.target).map((f) => f.target) ?? [];
|
|
405
442
|
console.log(`Installed template ${item.name} (${pages.length} files)`);
|
|
@@ -407,9 +444,62 @@ async function installItem(item, config, cwd) {
|
|
|
407
444
|
return fileHashes;
|
|
408
445
|
}
|
|
409
446
|
//#endregion
|
|
447
|
+
//#region src/utils/args.ts
|
|
448
|
+
/**
|
|
449
|
+
* Read `--key value` or `--key=value` from an argv slice. Ignores a following
|
|
450
|
+
* token that is itself a flag (so `--tokens --dry-run` yields undefined, not
|
|
451
|
+
* `--dry-run`).
|
|
452
|
+
*/
|
|
453
|
+
function flagValue(args, key) {
|
|
454
|
+
const eq = args.find((a) => a.startsWith(`--${key}=`));
|
|
455
|
+
if (eq) return eq.slice(key.length + 3);
|
|
456
|
+
const idx = args.indexOf(`--${key}`);
|
|
457
|
+
const next = idx !== -1 ? args[idx + 1] : void 0;
|
|
458
|
+
return next && !next.startsWith("--") ? next : void 0;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Extract positional (non-flag) arguments from an argv slice. Any token starting
|
|
462
|
+
* with `-` is dropped, as is the value token immediately following a flag named
|
|
463
|
+
* in `valueFlags` (e.g. `pnpm` in `--pm pnpm`), so component names never pick up
|
|
464
|
+
* a flag or its value.
|
|
465
|
+
*/
|
|
466
|
+
function positionalArgs(args, valueFlags = []) {
|
|
467
|
+
const out = [];
|
|
468
|
+
for (let i = 0; i < args.length; i++) {
|
|
469
|
+
const token = args[i];
|
|
470
|
+
if (token.startsWith("-")) {
|
|
471
|
+
const key = token.replace(/^--?/, "").split("=")[0];
|
|
472
|
+
const next = args[i + 1];
|
|
473
|
+
if (valueFlags.includes(key) && !token.includes("=") && next !== void 0 && !next.startsWith("-")) i++;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
out.push(token);
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Resolve the `--package-manager` / `--pm` flag to a validated PackageManager.
|
|
482
|
+
* Absent flag → `{ pm: undefined }` (fall back to auto-detection). Unknown value
|
|
483
|
+
* → `{ error }` so the caller can print it and exit non-zero.
|
|
484
|
+
*/
|
|
485
|
+
function resolvePackageManagerFlag(args) {
|
|
486
|
+
const raw = flagValue(args, "package-manager") ?? flagValue(args, "pm");
|
|
487
|
+
if (raw === void 0) return { pm: void 0 };
|
|
488
|
+
if (isPackageManager(raw)) return { pm: raw };
|
|
489
|
+
return { error: `Unknown package manager "${raw}". Use one of: pnpm, yarn, npm, bun.` };
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
410
492
|
//#region src/commands/create.ts
|
|
411
493
|
/** Version specifier used for every `@cascivo/*` dependency in generated apps. */
|
|
412
494
|
const CASCIVO_DEP = "latest";
|
|
495
|
+
/** Install-everything command for a package manager (`pnpm install`, `yarn`, …). */
|
|
496
|
+
function installAllCommand(pm) {
|
|
497
|
+
return pm === "yarn" ? "yarn" : `${pm} install`;
|
|
498
|
+
}
|
|
499
|
+
/** Run-a-script command for a package manager (`npm run dev` vs `pnpm dev`). */
|
|
500
|
+
function runScriptCommand(pm, script) {
|
|
501
|
+
return pm === "npm" ? `npm run ${script}` : `${pm} ${script}`;
|
|
502
|
+
}
|
|
413
503
|
/** Slug suitable for a union-member string literal: lower-kebab, alnum only. */
|
|
414
504
|
function slug(label) {
|
|
415
505
|
return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "section";
|
|
@@ -669,6 +759,7 @@ dist
|
|
|
669
759
|
`;
|
|
670
760
|
}
|
|
671
761
|
function readme(opts) {
|
|
762
|
+
const pm = opts.pm ?? "npm";
|
|
672
763
|
return `# ${opts.name}
|
|
673
764
|
|
|
674
765
|
A [cascivo](https://cascivo.com) app — Vite + React + TypeScript, pre-wired with
|
|
@@ -677,8 +768,8 @@ the cascivo app shell, side navigation, and the \`${opts.theme}\` theme.
|
|
|
677
768
|
## Develop
|
|
678
769
|
|
|
679
770
|
\`\`\`sh
|
|
680
|
-
|
|
681
|
-
|
|
771
|
+
${installAllCommand(pm)}
|
|
772
|
+
${runScriptCommand(pm, "dev")}
|
|
682
773
|
\`\`\`
|
|
683
774
|
|
|
684
775
|
## Structure
|
|
@@ -791,13 +882,6 @@ function buildScaffold(opts) {
|
|
|
791
882
|
}))
|
|
792
883
|
];
|
|
793
884
|
}
|
|
794
|
-
function flagValue$1(args, key) {
|
|
795
|
-
const eq = args.find((a) => a.startsWith(`--${key}=`));
|
|
796
|
-
if (eq) return eq.slice(key.length + 3);
|
|
797
|
-
const idx = args.indexOf(`--${key}`);
|
|
798
|
-
const next = idx !== -1 ? args[idx + 1] : void 0;
|
|
799
|
-
return next && !next.startsWith("--") ? next : void 0;
|
|
800
|
-
}
|
|
801
885
|
const DEFAULT_SECTIONS = [
|
|
802
886
|
"Dashboard",
|
|
803
887
|
"Reports",
|
|
@@ -805,9 +889,22 @@ const DEFAULT_SECTIONS = [
|
|
|
805
889
|
];
|
|
806
890
|
async function create(args, cwd = process.cwd()) {
|
|
807
891
|
const yes = args.includes("--yes") || args.includes("-y");
|
|
808
|
-
const nameArg = args
|
|
809
|
-
|
|
810
|
-
|
|
892
|
+
const nameArg = positionalArgs(args, [
|
|
893
|
+
"pm",
|
|
894
|
+
"package-manager",
|
|
895
|
+
"theme",
|
|
896
|
+
"sections",
|
|
897
|
+
"template"
|
|
898
|
+
])[0];
|
|
899
|
+
const themeArg = flagValue(args, "theme");
|
|
900
|
+
const sectionsArg = flagValue(args, "sections");
|
|
901
|
+
const pmFlag = resolvePackageManagerFlag(args);
|
|
902
|
+
if ("error" in pmFlag) {
|
|
903
|
+
console.error(pmFlag.error);
|
|
904
|
+
process.exitCode = 1;
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const pm = detectPackageManager(cwd, pmFlag.pm ? { override: pmFlag.pm } : {});
|
|
811
908
|
const rl = !yes && stdin.isTTY ? createInterface({
|
|
812
909
|
input: stdin,
|
|
813
910
|
output: stdout
|
|
@@ -825,7 +922,8 @@ async function create(args, cwd = process.cwd()) {
|
|
|
825
922
|
const opts = {
|
|
826
923
|
name,
|
|
827
924
|
theme: resolvedTheme,
|
|
828
|
-
sections: sections.length > 0 ? sections : DEFAULT_SECTIONS
|
|
925
|
+
sections: sections.length > 0 ? sections : DEFAULT_SECTIONS,
|
|
926
|
+
pm
|
|
829
927
|
};
|
|
830
928
|
const targetDir = join(cwd, name);
|
|
831
929
|
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
@@ -836,23 +934,83 @@ async function create(args, cwd = process.cwd()) {
|
|
|
836
934
|
const files = buildScaffold(opts);
|
|
837
935
|
for (const file of files) await writeFileSafe(join(targetDir, file.path), file.contents);
|
|
838
936
|
console.log(`\nCreated ${name} with the ${resolvedTheme} theme (${files.length} files).`);
|
|
839
|
-
const templateSpec = flagValue
|
|
937
|
+
const templateSpec = flagValue(args, "template");
|
|
840
938
|
if (templateSpec) {
|
|
841
939
|
const { add } = await Promise.resolve().then(() => add_exports);
|
|
842
|
-
const { loadConfig } = await import("./config-
|
|
940
|
+
const { loadConfig } = await import("./config-CYlTGdmE.mjs").then((n) => n.i);
|
|
843
941
|
console.log(`\nInstalling template "${templateSpec}"…`);
|
|
844
|
-
await add([templateSpec], await loadConfig(), {
|
|
942
|
+
await add([templateSpec], await loadConfig(), {
|
|
943
|
+
cwd: targetDir,
|
|
944
|
+
pm
|
|
945
|
+
});
|
|
845
946
|
}
|
|
846
947
|
console.log("\nNext steps:");
|
|
847
948
|
console.log(` cd ${name}`);
|
|
848
|
-
console.log(
|
|
849
|
-
console.log(
|
|
949
|
+
console.log(` ${installAllCommand(pm)}`);
|
|
950
|
+
console.log(` ${runScriptCommand(pm, "dev")}`);
|
|
850
951
|
} finally {
|
|
851
952
|
rl?.close();
|
|
852
953
|
}
|
|
853
954
|
}
|
|
854
955
|
//#endregion
|
|
855
956
|
//#region src/commands/doctor.ts
|
|
957
|
+
var doctor_exports = /* @__PURE__ */ __exportAll({
|
|
958
|
+
checkProjectDependencies: () => checkProjectDependencies,
|
|
959
|
+
isAdopterProject: () => isAdopterProject,
|
|
960
|
+
runDoctor: () => runDoctor,
|
|
961
|
+
stripCommentsAndStrings: () => stripCommentsAndStrings
|
|
962
|
+
});
|
|
963
|
+
/** Runtime packages copied cascivo source needs; the last is @cascivo/core's peer. */
|
|
964
|
+
const REQUIRED_RUNTIME_DEPS = [
|
|
965
|
+
"@cascivo/core",
|
|
966
|
+
"@cascivo/tokens",
|
|
967
|
+
"@cascivo/themes",
|
|
968
|
+
"@preact/signals-react"
|
|
969
|
+
];
|
|
970
|
+
/** Installed on demand by `cascivo add` when a component/chart declares them. */
|
|
971
|
+
const ON_DEMAND_DEPS = ["@cascivo/i18n", "@cascivo/charts"];
|
|
972
|
+
const CONFIG_FILES = [
|
|
973
|
+
"cascivo.config.ts",
|
|
974
|
+
"cascivo.config.js",
|
|
975
|
+
"cascivo.config.mjs"
|
|
976
|
+
];
|
|
977
|
+
/** Whether cwd looks like a cascivo adopter project (has a generated config). */
|
|
978
|
+
function isAdopterProject(cwd) {
|
|
979
|
+
return CONFIG_FILES.some((f) => existsSync(join(cwd, f)));
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Advisory check that the runtime dependencies copied source needs are declared
|
|
983
|
+
* in the project's package.json. Turns the opaque "cannot find module
|
|
984
|
+
* '@preact/signals-react'" build failure — the report's #4, where the peer was
|
|
985
|
+
* invisible — into a diagnosed condition with a fix. Adopter-only (gated on a
|
|
986
|
+
* cascivo.config by the caller); `@cascivo/i18n`/`@cascivo/charts` are advisory
|
|
987
|
+
* since not every project uses them.
|
|
988
|
+
*/
|
|
989
|
+
function checkProjectDependencies(cwd) {
|
|
990
|
+
let deps = {};
|
|
991
|
+
try {
|
|
992
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
993
|
+
deps = {
|
|
994
|
+
...pkg.dependencies,
|
|
995
|
+
...pkg.devDependencies
|
|
996
|
+
};
|
|
997
|
+
} catch {
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
1000
|
+
const pm = detectPackageManager(cwd);
|
|
1001
|
+
const findings = [];
|
|
1002
|
+
for (const pkg of REQUIRED_RUNTIME_DEPS) if (deps[pkg] === void 0) findings.push({
|
|
1003
|
+
package: pkg,
|
|
1004
|
+
required: true,
|
|
1005
|
+
hint: installHint(pm, [pkg])
|
|
1006
|
+
});
|
|
1007
|
+
for (const pkg of ON_DEMAND_DEPS) if (deps[pkg] === void 0) findings.push({
|
|
1008
|
+
package: pkg,
|
|
1009
|
+
required: false,
|
|
1010
|
+
hint: installHint(pm, [pkg])
|
|
1011
|
+
});
|
|
1012
|
+
return findings;
|
|
1013
|
+
}
|
|
856
1014
|
const BANNED_HOOKS = [
|
|
857
1015
|
"useState",
|
|
858
1016
|
"useEffect",
|
|
@@ -1090,13 +1248,21 @@ async function generate(args, config) {
|
|
|
1090
1248
|
}
|
|
1091
1249
|
//#endregion
|
|
1092
1250
|
//#region src/commands/init.ts
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1251
|
+
/**
|
|
1252
|
+
* Everything an app needs to build with copied cascivo source: the runtime
|
|
1253
|
+
* packages plus the `@preact/signals-react` peer of `@cascivo/core` (installed
|
|
1254
|
+
* explicitly rather than relying on peer auto-linking, which yarn never does and
|
|
1255
|
+
* npm skips when its install crashes). `@cascivo/i18n`/`@cascivo/charts` are
|
|
1256
|
+
* added on demand by `cascivo add` when a component or chart declares them.
|
|
1257
|
+
*/
|
|
1258
|
+
const RUNTIME_DEPS = [
|
|
1259
|
+
"@cascivo/core",
|
|
1260
|
+
"@cascivo/tokens",
|
|
1261
|
+
"@cascivo/themes",
|
|
1262
|
+
"@preact/signals-react"
|
|
1263
|
+
];
|
|
1264
|
+
/** Dev-only: `cascivo` provides the `CascadeConfig` type the generated config imports. */
|
|
1265
|
+
const DEV_DEPS = ["cascivo"];
|
|
1100
1266
|
async function promptTheme() {
|
|
1101
1267
|
const rl = createInterface({
|
|
1102
1268
|
input: stdin,
|
|
@@ -1121,23 +1287,73 @@ const config: CascadeConfig = {
|
|
|
1121
1287
|
export default config
|
|
1122
1288
|
`;
|
|
1123
1289
|
}
|
|
1290
|
+
const ESLINT_CONFIGS = [
|
|
1291
|
+
"eslint.config.js",
|
|
1292
|
+
"eslint.config.mjs",
|
|
1293
|
+
"eslint.config.cjs",
|
|
1294
|
+
"eslint.config.ts",
|
|
1295
|
+
".eslintrc",
|
|
1296
|
+
".eslintrc.js",
|
|
1297
|
+
".eslintrc.cjs",
|
|
1298
|
+
".eslintrc.json",
|
|
1299
|
+
".eslintrc.yml",
|
|
1300
|
+
".eslintrc.yaml"
|
|
1301
|
+
];
|
|
1302
|
+
/**
|
|
1303
|
+
* Copied cascivo source can trip a strict host ESLint config on stylistic rules.
|
|
1304
|
+
* If the project has an ESLint config, point at the scoping recipe rather than
|
|
1305
|
+
* editing the adopter's config for them.
|
|
1306
|
+
*/
|
|
1307
|
+
function hintEslintIfPresent(cwd) {
|
|
1308
|
+
if (!ESLINT_CONFIGS.some((f) => existsSync(join(cwd, f)))) return;
|
|
1309
|
+
console.log("\nESLint: copied cascivo source may trip a strict host config on stylistic rules.");
|
|
1310
|
+
console.log(" Scope them off your components dir — see docs/USING-WITH-STRICT-ESLINT.md");
|
|
1311
|
+
}
|
|
1312
|
+
/** The "here's everything you need" summary, printed once at the end of init. */
|
|
1313
|
+
function printDependencySummary() {
|
|
1314
|
+
console.log("\nDependencies");
|
|
1315
|
+
console.log(` runtime: ${RUNTIME_DEPS.join(" ")}`);
|
|
1316
|
+
console.log(` dev: ${DEV_DEPS.join(" ")} (types for cascivo.config.ts)`);
|
|
1317
|
+
console.log(" on demand: @cascivo/i18n, @cascivo/charts — added by `cascivo add` when a component or chart needs them");
|
|
1318
|
+
}
|
|
1124
1319
|
async function init(args = [], cwd = process.cwd()) {
|
|
1125
1320
|
const yes = args.includes("--yes") || args.includes("-y");
|
|
1321
|
+
const noInstall = args.includes("--no-install");
|
|
1126
1322
|
const themeArg = flagValue(args, "theme")?.toLowerCase();
|
|
1127
1323
|
if (themeArg !== void 0 && !THEMES.includes(themeArg)) {
|
|
1128
1324
|
console.error(`Unknown theme "${themeArg}". Available: ${THEMES.join(", ")}`);
|
|
1129
1325
|
process.exitCode = 1;
|
|
1130
1326
|
return;
|
|
1131
1327
|
}
|
|
1132
|
-
|
|
1328
|
+
const pmFlag = resolvePackageManagerFlag(args);
|
|
1329
|
+
if ("error" in pmFlag) {
|
|
1330
|
+
console.error(pmFlag.error);
|
|
1331
|
+
process.exitCode = 1;
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
const pm = detectPackageManager(cwd, pmFlag.pm ? { override: pmFlag.pm } : {});
|
|
1133
1335
|
const interactive = themeArg === void 0 && !yes && stdin.isTTY;
|
|
1134
1336
|
const theme = themeArg ?? (interactive ? await promptTheme() : "light");
|
|
1135
1337
|
await writeFileSafe(join(cwd, "cascivo.config.ts"), configFileContents(theme));
|
|
1136
1338
|
console.log(`\nCreated cascivo.config.ts (theme: ${theme})`);
|
|
1137
|
-
|
|
1339
|
+
if (noInstall) {
|
|
1340
|
+
console.log("\nSkipped install (--no-install). Install the dependencies yourself:");
|
|
1341
|
+
console.log(` ${installHint(pm, RUNTIME_DEPS)}`);
|
|
1342
|
+
console.log(` ${installHint(pm, DEV_DEPS, { dev: true })}`);
|
|
1343
|
+
} else {
|
|
1344
|
+
const runtimeOk = installPackages(RUNTIME_DEPS, cwd, { pm });
|
|
1345
|
+
const devOk = installPackages(DEV_DEPS, cwd, {
|
|
1346
|
+
pm,
|
|
1347
|
+
dev: true
|
|
1348
|
+
});
|
|
1349
|
+
if (!runtimeOk || !devOk) process.exitCode = 1;
|
|
1350
|
+
}
|
|
1351
|
+
console.log("\nImport the theme in your root CSS or entry file:");
|
|
1138
1352
|
console.log(` import '@cascivo/themes/${theme}.css'`);
|
|
1139
|
-
console.log(
|
|
1353
|
+
console.log("Then set the theme on your root element:");
|
|
1140
1354
|
console.log(` <html data-theme="${theme}">`);
|
|
1355
|
+
printDependencySummary();
|
|
1356
|
+
hintEslintIfPresent(cwd);
|
|
1141
1357
|
console.log("\nAdd components with: cascivo add <name>");
|
|
1142
1358
|
}
|
|
1143
1359
|
//#endregion
|
|
@@ -2015,18 +2231,22 @@ Scaffold a new ready-to-run app — Vite + React + TypeScript, pre-wired with th
|
|
|
2015
2231
|
cascivo app shell, side navigation, header, and a theme.
|
|
2016
2232
|
|
|
2017
2233
|
Options:
|
|
2018
|
-
--template <spec>
|
|
2019
|
-
--theme <name>
|
|
2020
|
-
--sections "<a, b>"
|
|
2021
|
-
--
|
|
2234
|
+
--template <spec> Start from a marketplace template (@ns/name or owner/repo/name)
|
|
2235
|
+
--theme <name> Theme to install (${THEME_LIST})
|
|
2236
|
+
--sections "<a, b>" Comma-separated nav section labels (one component each)
|
|
2237
|
+
--package-manager <pm> Package manager for README/next-steps (alias --pm; default: auto-detect)
|
|
2238
|
+
--yes, -y Accept defaults, never prompt`,
|
|
2022
2239
|
init: `Usage: cascivo init [options]
|
|
2023
2240
|
|
|
2024
|
-
Set up cascivo in the current project:
|
|
2025
|
-
|
|
2241
|
+
Set up cascivo in the current project: writes cascivo.config.ts and installs the
|
|
2242
|
+
runtime dependencies (@cascivo/core, @cascivo/tokens, @cascivo/themes, the
|
|
2243
|
+
@preact/signals-react peer) plus cascivo as a dev dependency for the config's types.
|
|
2026
2244
|
|
|
2027
2245
|
Options:
|
|
2028
|
-
--theme <name>
|
|
2029
|
-
--
|
|
2246
|
+
--theme <name> Theme to configure (${THEME_LIST})
|
|
2247
|
+
--package-manager <pm> Force pnpm | yarn | npm | bun (alias --pm; default: auto-detect)
|
|
2248
|
+
--no-install Write files only; print the install commands to run yourself
|
|
2249
|
+
--yes, -y Accept defaults, never prompt (implied when stdin is not a TTY)`,
|
|
2030
2250
|
add: `Usage: cascivo add <component...> [options]
|
|
2031
2251
|
|
|
2032
2252
|
Copy component source (TSX + CSS module) from the registry into your project,
|
|
@@ -2034,8 +2254,10 @@ resolving component dependencies. Also installs templates (@ns/name) and
|
|
|
2034
2254
|
third-party components (owner/repo/name).
|
|
2035
2255
|
|
|
2036
2256
|
Options:
|
|
2037
|
-
--dry-run
|
|
2038
|
-
--
|
|
2257
|
+
--dry-run Show what would be written without writing
|
|
2258
|
+
--package-manager <pm> Force pnpm | yarn | npm | bun (alias --pm; default: auto-detect)
|
|
2259
|
+
--no-install Don't install npm-distributed entries or deps; print the commands instead
|
|
2260
|
+
--yes, -y Skip confirmation prompts`,
|
|
2039
2261
|
list: `Usage: cascivo list [options]
|
|
2040
2262
|
|
|
2041
2263
|
List components available in the configured registry.
|
|
@@ -2089,10 +2311,13 @@ Options:
|
|
|
2089
2311
|
doctor: `Usage: cascivo doctor [options]
|
|
2090
2312
|
|
|
2091
2313
|
Check components in this repo for cascivo rule violations (banned React hooks,
|
|
2092
|
-
hardcoded strings, missing @cascivo/react exports).
|
|
2314
|
+
hardcoded strings, missing @cascivo/react exports). In an adopter project (one
|
|
2315
|
+
with a cascivo.config), also verifies the runtime dependencies copied source
|
|
2316
|
+
needs — @cascivo/core, @cascivo/tokens, @cascivo/themes, and the
|
|
2317
|
+
@preact/signals-react peer — are declared in package.json.
|
|
2093
2318
|
|
|
2094
2319
|
Options:
|
|
2095
|
-
--ci Exit non-zero when violations are found
|
|
2320
|
+
--ci Exit non-zero when violations or missing runtime deps are found
|
|
2096
2321
|
--drift Compare installed components against the registry (copy-paste drift)`,
|
|
2097
2322
|
audit: `Usage: cascivo audit --ai <paths...> [options]
|
|
2098
2323
|
|
|
@@ -2138,12 +2363,21 @@ async function run(args) {
|
|
|
2138
2363
|
case "init":
|
|
2139
2364
|
await init(rest);
|
|
2140
2365
|
break;
|
|
2141
|
-
case "add":
|
|
2142
|
-
|
|
2366
|
+
case "add": {
|
|
2367
|
+
const pmFlag = resolvePackageManagerFlag(rest);
|
|
2368
|
+
if ("error" in pmFlag) {
|
|
2369
|
+
console.error(pmFlag.error);
|
|
2370
|
+
process.exitCode = 1;
|
|
2371
|
+
break;
|
|
2372
|
+
}
|
|
2373
|
+
await add(positionalArgs(rest, ["pm", "package-manager"]), await loadConfig(), {
|
|
2143
2374
|
dryRun: rest.includes("--dry-run"),
|
|
2144
|
-
yes: rest.includes("--yes")
|
|
2375
|
+
yes: rest.includes("--yes"),
|
|
2376
|
+
noInstall: rest.includes("--no-install"),
|
|
2377
|
+
...pmFlag.pm ? { pm: pmFlag.pm } : {}
|
|
2145
2378
|
});
|
|
2146
2379
|
break;
|
|
2380
|
+
}
|
|
2147
2381
|
case "list":
|
|
2148
2382
|
await list(await loadConfig(), { installed: rest.includes("--installed") });
|
|
2149
2383
|
break;
|
|
@@ -2205,11 +2439,17 @@ async function run(args) {
|
|
|
2205
2439
|
const { runDoctorDrift } = await import("./drift-Ciw5qeEt.mjs");
|
|
2206
2440
|
await runDoctorDrift(await loadConfig());
|
|
2207
2441
|
} else {
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2442
|
+
const cwd = process.cwd();
|
|
2443
|
+
const result = await runDoctor(cwd);
|
|
2444
|
+
const { checkProjectDependencies, isAdopterProject } = await Promise.resolve().then(() => doctor_exports);
|
|
2445
|
+
const deps = isAdopterProject(cwd) ? checkProjectDependencies(cwd) : [];
|
|
2446
|
+
const missingRequired = deps.filter((d) => d.required);
|
|
2447
|
+
if (result.passed && deps.length === 0) console.log("No violations found.");
|
|
2210
2448
|
else {
|
|
2211
2449
|
for (const v of result.violations) console.error(`[${v.rule}] ${v.detail}\n ${v.file}`);
|
|
2212
|
-
|
|
2450
|
+
for (const d of missingRequired) console.error(`[missing-dependency] ${d.package} is not in package.json — copied cascivo source needs it. Install: ${d.hint}`);
|
|
2451
|
+
for (const d of deps.filter((x) => !x.required)) console.log(`[optional] ${d.package} is not installed; add it when a component or chart needs it: ${d.hint}`);
|
|
2452
|
+
if (ci && (result.violations.length > 0 || missingRequired.length > 0)) process.exitCode = 1;
|
|
2213
2453
|
}
|
|
2214
2454
|
}
|
|
2215
2455
|
break;
|