cascivo 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -140,7 +140,7 @@ function detectPackageManager(cwd = process.cwd(), opts = {}) {
|
|
|
140
140
|
const envPm = env.CASCIVO_PACKAGE_MANAGER;
|
|
141
141
|
if (isPackageManager(envPm)) return envPm;
|
|
142
142
|
const uaPm = pmFromUserAgent(env.npm_config_user_agent);
|
|
143
|
-
if (uaPm) return uaPm;
|
|
143
|
+
if (uaPm && !opts.preferLockfileOverUserAgent) return uaPm;
|
|
144
144
|
let current = cwd;
|
|
145
145
|
for (;;) {
|
|
146
146
|
for (const [file, pm] of PM_LOCKFILES) if (existsSync(join(current, file))) return pm;
|
|
@@ -151,7 +151,7 @@ function detectPackageManager(cwd = process.cwd(), opts = {}) {
|
|
|
151
151
|
if (parent === current) break;
|
|
152
152
|
current = parent;
|
|
153
153
|
}
|
|
154
|
-
return "npm";
|
|
154
|
+
return uaPm ?? "npm";
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* Pin every `@cascivo/*` package to an explicit version specifier.
|
|
@@ -191,4 +191,4 @@ function installHint(pm, packages, opts = {}) {
|
|
|
191
191
|
//#endregion
|
|
192
192
|
export { detectPackageManager as a, isPackageManager as c, config_exports as i, loadConfig as l, DEFAULT_CONFIG as n, installCommand as o, THEMES as r, installHint as s, CASCIVO_HOST as t, __exportAll as u };
|
|
193
193
|
|
|
194
|
-
//# sourceMappingURL=config-
|
|
194
|
+
//# sourceMappingURL=config-C8D_CmKI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-C8D_CmKI.mjs","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\n/**\n * Canonical (and only) host for hosted cascivo artifacts (registry.json, per-item\n * r/<name>.json, marketplace.json, /llms/*, /context/*). The legacy docs.cascivo.com\n * subdomain is retired and 301s here. Keep in sync with CASCIVO_HOST in packages/mcp.\n */\nexport const CASCIVO_HOST = 'https://cascivo.com'\n\n/** All first-party themes shipped by @cascivo/themes (selectable via data-theme). */\nexport const THEMES = [\n 'light',\n 'dark',\n 'warm',\n 'flat',\n 'minimal',\n 'midnight',\n 'pastel',\n 'brutalist',\n 'corporate',\n 'terminal',\n 'cyberpunk',\n 'arcade',\n] as const\n\nexport type ThemeName = (typeof THEMES)[number]\n\nexport type RegistryNamespaceConfig =\n | string\n | { url: string; headers?: Record<string, string>; params?: Record<string, string> }\n\nexport interface CascadeConfig {\n /** URL of the registry.json index. */\n registry: string\n /** Directory (relative to project root) where components are written. */\n outputDir: string\n /** Default theme imported by `cascade init`. */\n theme: ThemeName\n /** Namespace → registry URL template (with {name} placeholder) or auth config. */\n registries?: Record<string, RegistryNamespaceConfig>\n /** Whether to copy test files (*.contract.test.tsx) when adding components. */\n tests?: boolean\n}\n\nexport const DEFAULT_CONFIG: CascadeConfig = {\n // Canonical hosted registry index (served from the landing site, documented in\n // llms.txt). Prefer this over a branch's GitHub raw URL, which 404s for\n // unauthenticated/private-repo requests and breaks `cascivo list`/`add`.\n registry: `${CASCIVO_HOST}/registry.json`,\n outputDir: 'src/components/ui',\n theme: 'light',\n}\n\nconst CONFIG_FILES = ['cascivo.config.ts', 'cascivo.config.js', 'cascivo.config.mjs']\n\n/** Apply defaults over a (possibly partial) user config object. */\nexport function resolveConfig(partial: Partial<CascadeConfig> | null | undefined): CascadeConfig {\n return { ...DEFAULT_CONFIG, ...partial }\n}\n\n/** Let env vars override config — used by the MCP server to pass `outputDir`. */\nexport function applyEnvOverrides(\n config: CascadeConfig,\n env: NodeJS.ProcessEnv = process.env,\n): CascadeConfig {\n return {\n ...config,\n ...(env.CASCIVO_REGISTRY ? { registry: env.CASCIVO_REGISTRY } : {}),\n ...(env.CASCIVO_OUTPUT_DIR ? { outputDir: env.CASCIVO_OUTPUT_DIR } : {}),\n }\n}\n\n/**\n * Locate and load `cascivo.config.{ts,js,mjs}` from `cwd`. Falls back to the\n * default config when no file is found or the file cannot be loaded. Env vars\n * (`CASCIVO_REGISTRY`, `CASCIVO_OUTPUT_DIR`) take precedence.\n */\nexport async function loadConfig(cwd: string = process.cwd()): Promise<CascadeConfig> {\n for (const file of CONFIG_FILES) {\n const path = join(cwd, file)\n if (!existsSync(path)) continue\n try {\n const mod = (await import(pathToFileURL(path).href)) as {\n default?: Partial<CascadeConfig>\n }\n return applyEnvOverrides(resolveConfig(mod.default ?? (mod as Partial<CascadeConfig>)))\n } catch {\n // Unloadable config (e.g. unsupported TS syntax) — fall back to defaults.\n return applyEnvOverrides(resolveConfig(null))\n }\n }\n return applyEnvOverrides(resolveConfig(null))\n}\n\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm' | 'bun'\n\n/** Narrow an arbitrary string to a known PackageManager. */\nexport function isPackageManager(value: string | undefined): value is PackageManager {\n return value === 'pnpm' || value === 'yarn' || value === 'npm' || value === 'bun'\n}\n\n/**\n * Lock files in probe order. `bun.lock` (bun ≥ 1.2 text lockfile) sits beside the\n * legacy binary `bun.lockb`; `package-lock.json` is npm's marker so a walk that\n * reaches an npm workspace root still resolves to npm rather than the default.\n */\nconst PM_LOCKFILES: readonly [string, PackageManager][] = [\n ['pnpm-lock.yaml', 'pnpm'],\n ['yarn.lock', 'yarn'],\n ['bun.lockb', 'bun'],\n ['bun.lock', 'bun'],\n ['package-lock.json', 'npm'],\n]\n\n/** The package manager that invoked this process, from `npm_config_user_agent`. */\nfunction pmFromUserAgent(ua: string | undefined): PackageManager | undefined {\n if (!ua) return undefined\n const name = ua.split('/')[0]\n return isPackageManager(name) ? name : undefined\n}\n\n/** The `packageManager` corepack field of a package.json, if it names a known PM. */\nfunction pmFromPackageJson(dir: string): PackageManager | undefined {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as {\n packageManager?: string\n }\n const name = pkg.packageManager?.split('@')[0]\n return isPackageManager(name) ? name : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Detect the package manager in use. Precedence, highest first:\n * 1. explicit override (the `--package-manager`/`--pm` flag)\n * 2. `CASCIVO_PACKAGE_MANAGER` env var\n * 3. `npm_config_user_agent` (the PM that spawned the CLI, e.g. `pnpm dlx`)\n * 4. an upward walk from `cwd` for a lock file or `packageManager` field —\n * this is what makes detection work inside a workspace, where the lock\n * file lives at the repo root, not in the app subdirectory the user runs\n * the CLI from. The walk stops after a directory containing `.git`.\n * 5. default `npm`.\n */\nexport function detectPackageManager(\n cwd: string = process.cwd(),\n opts: {\n override?: string\n env?: NodeJS.ProcessEnv\n /**\n * Let a lock file found by walking up from `cwd` outrank `npm_config_user_agent`.\n *\n * For `init`/`add`, which run *inside* an existing project, the user agent is the right\n * first signal: it is the tool the developer just invoked. For `create` it is the wrong\n * one. `npx cascivo create` always reports a `npm/…` agent regardless of what the\n * surrounding workspace uses, and it short-circuited before the walk-up ever ran — so a\n * 2026-08-14 adopter scaffolding into a pnpm workspace with a root `pnpm-lock.yaml` was\n * told to run `npm install`. Where the new project LANDS is a stronger signal than which\n * launcher started the CLI.\n */\n preferLockfileOverUserAgent?: boolean\n } = {},\n): PackageManager {\n const env = opts.env ?? process.env\n\n if (isPackageManager(opts.override)) return opts.override\n\n const envPm = env.CASCIVO_PACKAGE_MANAGER\n if (isPackageManager(envPm)) return envPm\n\n const uaPm = pmFromUserAgent(env.npm_config_user_agent)\n if (uaPm && !opts.preferLockfileOverUserAgent) return uaPm\n\n let current = cwd\n for (;;) {\n for (const [file, pm] of PM_LOCKFILES) {\n if (existsSync(join(current, file))) return pm\n }\n const fromField = pmFromPackageJson(current)\n if (fromField) return fromField\n // A `.git` directory marks the repo root — do not walk past it.\n if (existsSync(join(current, '.git'))) break\n const parent = dirname(current)\n if (parent === current) break\n current = parent\n }\n\n // No lock file anywhere up the tree: fall back to the launcher, which for a brand-new\n // project in an empty directory is the only signal there is.\n return uaPm ?? 'npm'\n}\n\n/**\n * Pin every `@cascivo/*` package to an explicit version specifier.\n *\n * A bare name lets the package manager reuse whatever it already has resolvable. In a pnpm\n * workspace a sibling package's lockfile entry won and `@cascivo/i18n` resolved to **0.2.14**\n * while latest was **0.16.0** — then cascivo warned the adopter about the version it had\n * just installed itself. `add name@latest` (or the registry's known floor) makes the intent\n * explicit instead of leaving it to resolution order.\n *\n * Non-cascivo packages (`@preact/signals-react`) keep their bare name: their version is the\n * app's business, and cascivo has no floor to assert.\n */\nexport function pinSpecifier(pkg: string, floors: Record<string, string> = {}): string {\n if (!pkg.startsWith('@cascivo/') && pkg !== 'cascivo') return pkg\n if (pkg.includes('@', 1)) return pkg // already carries an explicit version\n const floor = floors[pkg]\n // `>=x.y.z` is not an installable specifier on its own; widen it to a range the package\n // manager understands, so a known floor is honoured rather than silently ignored.\n if (floor?.startsWith('>=')) return `${pkg}@${floor.slice(2)} - x`\n return `${pkg}@latest`\n}\n\n/** The install subcommand each package manager uses to add dependencies. */\nexport function installCommand(\n pm: PackageManager,\n packages: string[],\n opts: { dev?: boolean; floors?: Record<string, string>; pin?: string } = {},\n): [string, string[]] {\n const verb = pm === 'npm' ? 'install' : 'add'\n const devFlag = opts.dev ? [pm === 'npm' ? '--save-dev' : '-D'] : []\n const specs = packages.map((p) =>\n opts.pin && (p.startsWith('@cascivo/') || p === 'cascivo')\n ? `${p}@${opts.pin}`\n : pinSpecifier(p, opts.floors ?? {}),\n )\n return [pm, [verb, ...devFlag, ...specs]]\n}\n\n/** Human-readable install command a user can copy-paste, e.g. `pnpm add -D cascivo`. */\nexport function installHint(\n pm: PackageManager,\n packages: string[],\n opts: { dev?: boolean } = {},\n): string {\n const [cmd, args] = installCommand(pm, packages, opts)\n return `${cmd} ${args.join(' ')}`\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.17.
|
|
2
|
+
"version": "0.17.1",
|
|
3
3
|
"tokens": [
|
|
4
4
|
{ "name": "--cascivo-gray-0", "resolvedDefault": "oklch(1 0 0)" },
|
|
5
5
|
{ "name": "--cascivo-gray-50", "resolvedDefault": "oklch(0.985 0.002 264)" },
|
|
@@ -696,7 +696,8 @@
|
|
|
696
696
|
{
|
|
697
697
|
"name": "CodeSnippet",
|
|
698
698
|
"props": [
|
|
699
|
-
{ "name": "code", "type": "string", "required":
|
|
699
|
+
{ "name": "code", "type": "string", "required": false },
|
|
700
|
+
{ "name": "children", "type": "string", "required": false },
|
|
700
701
|
{ "name": "variant", "type": "'inline' | 'single' | 'multi'", "required": false },
|
|
701
702
|
{ "name": "language", "type": "'bash' | 'css' | 'js' | 'ts'", "required": false },
|
|
702
703
|
{ "name": "terminal", "type": "boolean", "required": false },
|
|
@@ -2142,8 +2143,8 @@
|
|
|
2142
2143
|
{
|
|
2143
2144
|
"name": "PageHeader",
|
|
2144
2145
|
"props": [
|
|
2145
|
-
{ "name": "title", "type": "
|
|
2146
|
-
{ "name": "description", "type": "
|
|
2146
|
+
{ "name": "title", "type": "ReactNode", "required": true },
|
|
2147
|
+
{ "name": "description", "type": "ReactNode", "required": false },
|
|
2147
2148
|
{ "name": "breadcrumb", "type": "ReactNode", "required": false },
|
|
2148
2149
|
{ "name": "actions", "type": "ReactNode", "required": false },
|
|
2149
2150
|
{ "name": "className", "type": "string", "required": false }
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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-
|
|
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-C8D_CmKI.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 writeLock, c as findComponent, i as updateLockEntry, n as readLock, o as fetchRegistry, r as sha256, s as fileName, t as createLock } from "./lock-CW8UuEPJ.mjs";
|
|
@@ -514,11 +514,11 @@ function resolvePackageManagerFlag(args) {
|
|
|
514
514
|
* and GETTING-STARTED.md tells adopters to pin exactly. Regenerate with `pnpm regen`.
|
|
515
515
|
*/
|
|
516
516
|
const CASCIVO_VERSIONS = {
|
|
517
|
-
"@cascivo/react": "0.17.
|
|
518
|
-
"@cascivo/themes": "0.4.
|
|
519
|
-
"@cascivo/charts": "0.17.
|
|
520
|
-
"@cascivo/icons": "0.3.
|
|
521
|
-
"@cascivo/eslint-config": "0.2.
|
|
517
|
+
"@cascivo/react": "0.17.1",
|
|
518
|
+
"@cascivo/themes": "0.4.12",
|
|
519
|
+
"@cascivo/charts": "0.17.1",
|
|
520
|
+
"@cascivo/icons": "0.3.9",
|
|
521
|
+
"@cascivo/eslint-config": "0.2.3"
|
|
522
522
|
};
|
|
523
523
|
/** `@cascivo/core`'s declared `@preact/signals-react` peer range. */
|
|
524
524
|
const SIGNALS_PEER = ">=3.0.0";
|
|
@@ -683,7 +683,7 @@ function indexHtml(opts) {
|
|
|
683
683
|
<head>
|
|
684
684
|
<meta charset="UTF-8" />
|
|
685
685
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
686
|
-
<title>${opts.name}</title>
|
|
686
|
+
<title>${brandName(opts.name)}</title>
|
|
687
687
|
<style>
|
|
688
688
|
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
|
|
689
689
|
cascivo.platform, cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
|
|
@@ -737,7 +737,7 @@ if (root) {
|
|
|
737
737
|
function viteEnv() {
|
|
738
738
|
return `/// <reference types="vite/client" />\n`;
|
|
739
739
|
}
|
|
740
|
-
function appTsx(
|
|
740
|
+
function appTsx(sections) {
|
|
741
741
|
const sectionImports = sections.map((s) => `import { ${s.component} } from './sections/${s.component}'`).join("\n");
|
|
742
742
|
const unionType = sections.map((s) => `'${s.key}'`).join(" | ");
|
|
743
743
|
const navItems = sections.map((s) => ` {
|
|
@@ -750,19 +750,10 @@ function appTsx(opts, sections) {
|
|
|
750
750
|
},`).join("\n");
|
|
751
751
|
const renderedSections = sections.map((s) => ` {section.value === '${s.key}' && <${s.component} />}`).join("\n");
|
|
752
752
|
return `'use client'
|
|
753
|
-
import {
|
|
754
|
-
|
|
755
|
-
ShellHeader,
|
|
756
|
-
SideNav,
|
|
757
|
-
signal,
|
|
758
|
-
useSignals,
|
|
759
|
-
type SideNavItem,
|
|
760
|
-
} from '@cascivo/react'
|
|
753
|
+
import { signal, useSignals, type SideNavItem } from '@cascivo/react'
|
|
754
|
+
import { Shell } from './Shell'
|
|
761
755
|
${sectionImports}
|
|
762
756
|
|
|
763
|
-
import '@cascivo/themes/${opts.theme}.css'
|
|
764
|
-
import '@cascivo/react/styles.css'
|
|
765
|
-
|
|
766
757
|
type Section = ${unionType}
|
|
767
758
|
|
|
768
759
|
const section = signal<Section>('${sections[0].key}')
|
|
@@ -774,12 +765,63 @@ export default function App() {
|
|
|
774
765
|
${navItems}
|
|
775
766
|
]
|
|
776
767
|
|
|
768
|
+
return (
|
|
769
|
+
<Shell navItems={navItems}>
|
|
770
|
+
${renderedSections}
|
|
771
|
+
</Shell>
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
`;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* The app shell, as its own component with a `children` slot.
|
|
778
|
+
*
|
|
779
|
+
* Split out of `App.tsx` because the shell composition — AppShell + ShellHeader + SideNav —
|
|
780
|
+
* is the valuable part of the scaffold, and it used to be welded to the signal-driven
|
|
781
|
+
* section switcher. A 2026-08-14 adopter prompted for "a dashboard with Vite and React
|
|
782
|
+
* Router", then deleted `App.tsx` and all of `src/sections/` — the majority of what `create`
|
|
783
|
+
* generated — and re-derived this wiring by hand.
|
|
784
|
+
*
|
|
785
|
+
* Now adding a router means deleting `App.tsx` + `src/sections/` and rendering `<Shell>` from
|
|
786
|
+
* the root route's layout, with `navItems` carrying `href` instead of `onClick`. Nothing in
|
|
787
|
+
* here needs to change.
|
|
788
|
+
*/
|
|
789
|
+
function shellTsx(opts) {
|
|
790
|
+
return `'use client'
|
|
791
|
+
import { AppShell, ShellHeader, SideNav, type SideNavItem } from '@cascivo/react'
|
|
792
|
+
import type { ReactNode } from 'react'
|
|
793
|
+
|
|
794
|
+
import '@cascivo/themes/${opts.theme}.css'
|
|
795
|
+
// No '@cascivo/react/styles.css' here. On a bundler build each component imports its
|
|
796
|
+
// own CSS, so you ship exactly what you use — this app emits well under 100 kB of entry
|
|
797
|
+
// CSS instead of the ~273 kB aggregate sheet. Import the aggregate ONLY if you drop the
|
|
798
|
+
// bundler (CDN / single-file setup). See https://cascivo.com/docs/getting-started.md
|
|
799
|
+
// The theme import above is always required — themes are never automatic.
|
|
800
|
+
|
|
801
|
+
export interface ShellProps {
|
|
802
|
+
/** Side-nav entries. Use \`href\` for a routed app, \`onClick\` for local state. */
|
|
803
|
+
navItems: SideNavItem[]
|
|
804
|
+
children: ReactNode
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* App shell: header + side nav + a content slot.
|
|
809
|
+
*
|
|
810
|
+
* Adding a router? Keep this file. Delete \`App.tsx\` and \`src/sections/\`, render
|
|
811
|
+
* \`<Shell navItems={…}>\` from your root route's layout with your \`<Outlet />\` as children,
|
|
812
|
+
* and give each nav item an \`href\` instead of an \`onClick\`.
|
|
813
|
+
*
|
|
814
|
+
* For those hrefs to become real router links, call \`setLinkComponent\` ONCE at startup in
|
|
815
|
+
* \`main.tsx\` — see the "Adding a router" section of README.md for the exact snippet, or
|
|
816
|
+
* https://cascivo.com/docs/using-with-a-router.md for the full recipe.
|
|
817
|
+
*/
|
|
818
|
+
export function Shell({ navItems, children }: ShellProps) {
|
|
777
819
|
return (
|
|
778
820
|
<AppShell
|
|
779
821
|
header={<ShellHeader brand={{ name: '${brandName(opts.name).replace(/'/g, "\\'")}' }} />}
|
|
780
822
|
nav={<SideNav items={navItems} />}
|
|
781
823
|
>
|
|
782
|
-
|
|
824
|
+
{children}
|
|
783
825
|
</AppShell>
|
|
784
826
|
)
|
|
785
827
|
}
|
|
@@ -887,10 +929,34 @@ ${runScriptCommand(pm, "dev")}
|
|
|
887
929
|
|
|
888
930
|
## Structure
|
|
889
931
|
|
|
890
|
-
- \`src/
|
|
932
|
+
- \`src/Shell.tsx\` — the app shell (header + side nav + content slot). Router-agnostic.
|
|
933
|
+
- \`src/App.tsx\` — nav items and which section is showing
|
|
891
934
|
- \`src/sections/\` — one component per nav item
|
|
892
935
|
|
|
893
936
|
Add more components with \`npx cascivo add <component>\`.
|
|
937
|
+
|
|
938
|
+
## Adding a router
|
|
939
|
+
|
|
940
|
+
This app switches sections with a signal, not a router. To add one (React Router,
|
|
941
|
+
TanStack Router, …):
|
|
942
|
+
|
|
943
|
+
1. **Keep \`src/Shell.tsx\`.** Delete \`src/App.tsx\` and \`src/sections/\`.
|
|
944
|
+
2. Render \`<Shell navItems={…}>\` from your root route's layout, with your \`<Outlet />\`
|
|
945
|
+
as its children.
|
|
946
|
+
3. Give each nav item an \`href\` instead of \`onClick\`.
|
|
947
|
+
4. Register your router's Link **once** at startup, in \`src/main.tsx\`:
|
|
948
|
+
|
|
949
|
+
\`\`\`tsx
|
|
950
|
+
import { setLinkComponent } from '@cascivo/react'
|
|
951
|
+
import type { LinkComponentProps } from '@cascivo/react'
|
|
952
|
+
import { Link } from 'react-router'
|
|
953
|
+
|
|
954
|
+
setLinkComponent(({ href, ...rest }: LinkComponentProps) => <Link to={href ?? '#'} {...rest} />)
|
|
955
|
+
\`\`\`
|
|
956
|
+
|
|
957
|
+
That one call makes \`SideNav\`, \`ShellHeader\` and \`Breadcrumb\` render real router links.
|
|
958
|
+
Links you write in page content use \`<Link asChild>\` instead — two kinds of link, two
|
|
959
|
+
mechanisms. Full recipe: https://cascivo.com/docs/using-with-a-router.md
|
|
894
960
|
`;
|
|
895
961
|
}
|
|
896
962
|
function agentsMd(opts) {
|
|
@@ -938,6 +1004,28 @@ This app's declared layer order (in \`index.html\`):
|
|
|
938
1004
|
}
|
|
939
1005
|
\`\`\`
|
|
940
1006
|
|
|
1007
|
+
## Routing
|
|
1008
|
+
|
|
1009
|
+
If you add a router, keep \`src/Shell.tsx\` and delete \`src/App.tsx\` + \`src/sections/\`.
|
|
1010
|
+
|
|
1011
|
+
cascivo links come in **two kinds**, wired two different ways. Do not intercept
|
|
1012
|
+
\`onClick\`, and do not hand-wrap nav items:
|
|
1013
|
+
|
|
1014
|
+
1. **Config-driven navs** (\`SideNav\`, \`ShellHeader\`, \`Breadcrumb\`, \`Switcher\`) render
|
|
1015
|
+
through a module singleton. Register your router's Link once, in \`src/main.tsx\`:
|
|
1016
|
+
\`setLinkComponent(({ href, ...rest }: LinkComponentProps) => <Link to={href ?? '#'} {...rest} />)\`
|
|
1017
|
+
2. **Links in page content** use \`asChild\`:
|
|
1018
|
+
\`<Link asChild><RouterLink to="/x">x</RouterLink></Link>\`
|
|
1019
|
+
|
|
1020
|
+
Full recipe: https://cascivo.com/docs/using-with-a-router.md
|
|
1021
|
+
|
|
1022
|
+
## Types
|
|
1023
|
+
|
|
1024
|
+
The vocabulary types are on a subpath: \`import type { Tone } from '@cascivo/react/types'\`
|
|
1025
|
+
(also \`Progress\`, \`SpaceStep\`). \`Status.status\` and \`Badge.variant\` use them, so a
|
|
1026
|
+
\`Record<MyState, Tone>\` is the supported way to map domain states onto tones. **Never**
|
|
1027
|
+
add \`@cascivo/core\` to this app's dependencies — it is transitive here.
|
|
1028
|
+
|
|
941
1029
|
More: cascivo's machine-readable guide is at https://cascivo.com/llms.txt.
|
|
942
1030
|
`;
|
|
943
1031
|
}
|
|
@@ -995,7 +1083,11 @@ function buildScaffold(opts) {
|
|
|
995
1083
|
},
|
|
996
1084
|
{
|
|
997
1085
|
path: "src/App.tsx",
|
|
998
|
-
contents: appTsx(
|
|
1086
|
+
contents: appTsx(sections)
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
path: "src/Shell.tsx",
|
|
1090
|
+
contents: shellTsx(opts)
|
|
999
1091
|
},
|
|
1000
1092
|
...sections.map((s) => ({
|
|
1001
1093
|
path: `src/sections/${s.component}.tsx`,
|
|
@@ -1025,7 +1117,10 @@ async function create(args, cwd = process.cwd()) {
|
|
|
1025
1117
|
process.exitCode = 1;
|
|
1026
1118
|
return;
|
|
1027
1119
|
}
|
|
1028
|
-
const pm = detectPackageManager(cwd,
|
|
1120
|
+
const pm = detectPackageManager(cwd, {
|
|
1121
|
+
preferLockfileOverUserAgent: true,
|
|
1122
|
+
...pmFlag.pm ? { override: pmFlag.pm } : {}
|
|
1123
|
+
});
|
|
1029
1124
|
const rl = !yes && stdin.isTTY ? createInterface({
|
|
1030
1125
|
input: stdin,
|
|
1031
1126
|
output: stdout
|
|
@@ -1058,7 +1153,7 @@ async function create(args, cwd = process.cwd()) {
|
|
|
1058
1153
|
const templateSpec = flagValue(args, "template");
|
|
1059
1154
|
if (templateSpec) {
|
|
1060
1155
|
const { add } = await Promise.resolve().then(() => add_exports);
|
|
1061
|
-
const { loadConfig } = await import("./config-
|
|
1156
|
+
const { loadConfig } = await import("./config-C8D_CmKI.mjs").then((n) => n.i);
|
|
1062
1157
|
console.log(`\nInstalling template "${templateSpec}"…`);
|
|
1063
1158
|
await add([templateSpec], await loadConfig(), {
|
|
1064
1159
|
cwd: targetDir,
|
|
@@ -1069,6 +1164,13 @@ async function create(args, cwd = process.cwd()) {
|
|
|
1069
1164
|
console.log(` cd ${name}`);
|
|
1070
1165
|
console.log(` ${installAllCommand(pm)}`);
|
|
1071
1166
|
console.log(` ${runScriptCommand(pm, "dev")}`);
|
|
1167
|
+
console.log("\nGood to know:");
|
|
1168
|
+
console.log(" No cascivo.config.ts is written — this app uses the prebuilt @cascivo/react");
|
|
1169
|
+
console.log(" packages and never copies source. `cascivo add <component>` writes the");
|
|
1170
|
+
console.log(" config itself the first time you vendor a component.");
|
|
1171
|
+
console.log("\n Adding a router? Keep src/Shell.tsx, delete src/App.tsx + src/sections/,");
|
|
1172
|
+
console.log(" and register your Link once with setLinkComponent — see");
|
|
1173
|
+
console.log(" https://cascivo.com/docs/using-with-a-router.md");
|
|
1072
1174
|
} finally {
|
|
1073
1175
|
rl?.close();
|
|
1074
1176
|
}
|