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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as detectPackageManager, c as __exportAll, n as DEFAULT_CONFIG$1, o as installCommand, r as THEMES, s as loadConfig, t as CASCIVO_HOST } from "./config-BsyJbMvD.mjs";
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 package manager, inheriting stdio so
18
- * the user sees install progress. Returns false if the install failed.
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 [cmd, args] = installCommand(detectPackageManager(cwd), packages);
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
- * Copied components read `--cascivo-*` custom properties; without
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
- deps = {
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(" npm install @cascivo/themes");
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
- console.log(`"${entry.name}" is distributed as the npm package ${pkg}.`);
336
- console.log(`Install it with: npm install ${pkg}`);
337
- console.log(`Then import: import { ${entry.meta.name} } from '${pkg}'`);
338
- if (entry.styles) console.log(`And import its stylesheet once: import '${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 (missingDeps.size > 0) installPackages([...missingDeps]);
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: npm install ${v.pkg}@latest`);
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) installPackages([...missingDeps]);
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
- npm install
681
- npm run dev
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.find((a) => !a.startsWith("-"));
809
- const themeArg = flagValue$1(args, "theme");
810
- const sectionsArg = flagValue$1(args, "sections");
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$1(args, "template");
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-BsyJbMvD.mjs").then((n) => n.i);
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(), { cwd: targetDir });
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(" npm install");
849
- console.log(" npm run dev");
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
- function flagValue(args, key) {
1094
- const eq = args.find((a) => a.startsWith(`--${key}=`));
1095
- if (eq) return eq.slice(key.length + 3);
1096
- const idx = args.indexOf(`--${key}`);
1097
- const next = idx !== -1 ? args[idx + 1] : void 0;
1098
- return next && !next.startsWith("--") ? next : void 0;
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
- installPackages(["@cascivo/core", "@cascivo/tokens"], cwd);
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
- console.log("Import the theme in your root CSS or entry file:");
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(`Then set the theme on your root element:`);
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> Start from a marketplace template (@ns/name or owner/repo/name)
2019
- --theme <name> Theme to install (${THEME_LIST})
2020
- --sections "<a, b>" Comma-separated nav section labels (one component each)
2021
- --yes, -y Accept defaults, never prompt`,
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: installs @cascivo/core + @cascivo/tokens
2025
- and writes cascivo.config.ts.
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> Theme to configure (${THEME_LIST})
2029
- --yes, -y Accept defaults, never prompt (implied when stdin is not a TTY)`,
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 Show what would be written without writing
2038
- --yes, -y Skip confirmation prompts`,
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
- await add(rest, await loadConfig(), {
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 result = await runDoctor(process.cwd());
2209
- if (result.passed) console.log("No violations found.");
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
- if (ci) process.exitCode = 1;
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;