cascivo 0.7.2 → 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.
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.
|
|
518
|
-
"@cascivo/themes": "0.4.
|
|
519
|
-
"@cascivo/charts": "0.
|
|
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";
|
|
@@ -614,7 +614,9 @@ function packageJson(opts) {
|
|
|
614
614
|
build: "tsc && vite build",
|
|
615
615
|
preview: "vite preview",
|
|
616
616
|
typecheck: "tsc --noEmit",
|
|
617
|
-
lint: "eslint ."
|
|
617
|
+
lint: "eslint .",
|
|
618
|
+
format: "prettier --write .",
|
|
619
|
+
"format:check": "prettier --check ."
|
|
618
620
|
},
|
|
619
621
|
dependencies: {
|
|
620
622
|
"@cascivo/react": V["@cascivo/react"],
|
|
@@ -631,7 +633,9 @@ function packageJson(opts) {
|
|
|
631
633
|
"@vitejs/plugin-react": "^5.0.0",
|
|
632
634
|
eslint: "^9.0.0",
|
|
633
635
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
636
|
+
prettier: "^3.0.0",
|
|
634
637
|
typescript: "^5.7.0",
|
|
638
|
+
"typescript-eslint": "^8.0.0",
|
|
635
639
|
vite: "^7.0.0"
|
|
636
640
|
}
|
|
637
641
|
};
|
|
@@ -679,7 +683,7 @@ function indexHtml(opts) {
|
|
|
679
683
|
<head>
|
|
680
684
|
<meta charset="UTF-8" />
|
|
681
685
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
682
|
-
<title>${opts.name}</title>
|
|
686
|
+
<title>${brandName(opts.name)}</title>
|
|
683
687
|
<style>
|
|
684
688
|
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
|
|
685
689
|
cascivo.platform, cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
|
|
@@ -699,10 +703,12 @@ function indexHtml(opts) {
|
|
|
699
703
|
padding: 0;
|
|
700
704
|
}
|
|
701
705
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
+
@layer cascivo.base {
|
|
707
|
+
html,
|
|
708
|
+
body,
|
|
709
|
+
#root {
|
|
710
|
+
height: 100%;
|
|
711
|
+
}
|
|
706
712
|
}
|
|
707
713
|
</style>
|
|
708
714
|
</head>
|
|
@@ -731,7 +737,7 @@ if (root) {
|
|
|
731
737
|
function viteEnv() {
|
|
732
738
|
return `/// <reference types="vite/client" />\n`;
|
|
733
739
|
}
|
|
734
|
-
function appTsx(
|
|
740
|
+
function appTsx(sections) {
|
|
735
741
|
const sectionImports = sections.map((s) => `import { ${s.component} } from './sections/${s.component}'`).join("\n");
|
|
736
742
|
const unionType = sections.map((s) => `'${s.key}'`).join(" | ");
|
|
737
743
|
const navItems = sections.map((s) => ` {
|
|
@@ -744,19 +750,10 @@ function appTsx(opts, sections) {
|
|
|
744
750
|
},`).join("\n");
|
|
745
751
|
const renderedSections = sections.map((s) => ` {section.value === '${s.key}' && <${s.component} />}`).join("\n");
|
|
746
752
|
return `'use client'
|
|
747
|
-
import {
|
|
748
|
-
|
|
749
|
-
ShellHeader,
|
|
750
|
-
SideNav,
|
|
751
|
-
signal,
|
|
752
|
-
useSignals,
|
|
753
|
-
type SideNavItem,
|
|
754
|
-
} from '@cascivo/react'
|
|
753
|
+
import { signal, useSignals, type SideNavItem } from '@cascivo/react'
|
|
754
|
+
import { Shell } from './Shell'
|
|
755
755
|
${sectionImports}
|
|
756
756
|
|
|
757
|
-
import '@cascivo/themes/${opts.theme}.css'
|
|
758
|
-
import '@cascivo/react/styles.css'
|
|
759
|
-
|
|
760
757
|
type Section = ${unionType}
|
|
761
758
|
|
|
762
759
|
const section = signal<Section>('${sections[0].key}')
|
|
@@ -768,36 +765,80 @@ export default function App() {
|
|
|
768
765
|
${navItems}
|
|
769
766
|
]
|
|
770
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) {
|
|
771
819
|
return (
|
|
772
820
|
<AppShell
|
|
773
821
|
header={<ShellHeader brand={{ name: '${brandName(opts.name).replace(/'/g, "\\'")}' }} />}
|
|
774
822
|
nav={<SideNav items={navItems} />}
|
|
775
823
|
>
|
|
776
|
-
|
|
824
|
+
{children}
|
|
777
825
|
</AppShell>
|
|
778
826
|
)
|
|
779
827
|
}
|
|
780
828
|
`;
|
|
781
829
|
}
|
|
782
830
|
function sectionTsx(section) {
|
|
783
|
-
return `import { Card, CardContent, CardHeader, CardTitle, Heading, Text } from '@cascivo/react'
|
|
831
|
+
return `import { Card, CardContent, CardHeader, CardTitle, Flex, Heading, Text } from '@cascivo/react'
|
|
784
832
|
|
|
785
833
|
export function ${section.component}() {
|
|
786
834
|
return (
|
|
787
|
-
<
|
|
788
|
-
|
|
789
|
-
display: 'grid',
|
|
790
|
-
gap: 'var(--cascivo-space-6)',
|
|
791
|
-
padding: 'var(--cascivo-space-6)',
|
|
792
|
-
maxWidth: '64rem',
|
|
793
|
-
}}
|
|
794
|
-
>
|
|
795
|
-
<div style={{ display: 'grid', gap: 'var(--cascivo-space-2)' }}>
|
|
835
|
+
<Flex gap={6}>
|
|
836
|
+
<Flex gap={2}>
|
|
796
837
|
<Heading level={1}>${section.label}</Heading>
|
|
797
838
|
<Text muted>
|
|
798
839
|
Edit <code>src/sections/${section.component}.tsx</code> to build out this page.
|
|
799
840
|
</Text>
|
|
800
|
-
</
|
|
841
|
+
</Flex>
|
|
801
842
|
|
|
802
843
|
<Card>
|
|
803
844
|
<CardHeader>
|
|
@@ -810,7 +851,7 @@ export function ${section.component}() {
|
|
|
810
851
|
</Text>
|
|
811
852
|
</CardContent>
|
|
812
853
|
</Card>
|
|
813
|
-
</
|
|
854
|
+
</Flex>
|
|
814
855
|
)
|
|
815
856
|
}
|
|
816
857
|
`;
|
|
@@ -826,22 +867,45 @@ export function ${section.component}() {
|
|
|
826
867
|
*/
|
|
827
868
|
function eslintConfig() {
|
|
828
869
|
return `import js from '@eslint/js'
|
|
870
|
+
import tseslint from 'typescript-eslint'
|
|
829
871
|
import reactHooks from 'eslint-plugin-react-hooks'
|
|
830
872
|
import cascivo from '@cascivo/eslint-config'
|
|
831
873
|
|
|
832
874
|
export default [
|
|
875
|
+
{ ignores: ['dist/**'] },
|
|
833
876
|
js.configs.recommended,
|
|
877
|
+
// Registers the TypeScript parser and the .ts/.tsx \`files\` patterns. WITHOUT THIS ESLINT
|
|
878
|
+
// LINTS NOTHING: its default \`files\` is **/*.{js,cjs,mjs}, so every file in this app is
|
|
879
|
+
// skipped with "File ignored because no matching configuration was supplied" and the
|
|
880
|
+
// \`lint\` script exits 0 having checked zero files.
|
|
881
|
+
...tseslint.configs.recommended,
|
|
834
882
|
// NOTE the \`.flat\` — the plugin exports both \`configs['recommended-latest']\` (the legacy
|
|
835
883
|
// eslintrc shape, which applies NOTHING here and reports no error) and this one.
|
|
836
884
|
reactHooks.configs.flat['recommended-latest'],
|
|
837
885
|
// Spread LAST — flat config is last-wins. This turns off \`react-hooks/immutability\`,
|
|
838
|
-
// which reports cascivo's signal writes (\`signal.value = next\`) as errors.
|
|
839
|
-
//
|
|
886
|
+
// which reports cascivo's signal writes (\`signal.value = next\`) as errors. That rule
|
|
887
|
+
// fires on the very first \`signal.value = x\` you write, so this is not optional wiring:
|
|
888
|
+
// see https://cascivo.com/docs/using-with-strict-eslint.md
|
|
840
889
|
...cascivo,
|
|
841
|
-
{ ignores: ['dist/**'] },
|
|
842
890
|
]
|
|
843
891
|
`;
|
|
844
892
|
}
|
|
893
|
+
/** Prettier config matching the style the scaffold's own generated source is written in. */
|
|
894
|
+
function prettierrc() {
|
|
895
|
+
return JSON.stringify({
|
|
896
|
+
semi: false,
|
|
897
|
+
singleQuote: true,
|
|
898
|
+
printWidth: 100
|
|
899
|
+
}, null, 2) + "\n";
|
|
900
|
+
}
|
|
901
|
+
function prettierIgnore() {
|
|
902
|
+
return `dist
|
|
903
|
+
node_modules
|
|
904
|
+
# Vendored cascivo source is formatted upstream — reformatting it makes every
|
|
905
|
+
# \`cascivo add\` update a merge conflict.
|
|
906
|
+
src/components/ui/
|
|
907
|
+
`;
|
|
908
|
+
}
|
|
845
909
|
function gitignore() {
|
|
846
910
|
return `node_modules
|
|
847
911
|
dist
|
|
@@ -865,10 +929,34 @@ ${runScriptCommand(pm, "dev")}
|
|
|
865
929
|
|
|
866
930
|
## Structure
|
|
867
931
|
|
|
868
|
-
- \`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
|
|
869
934
|
- \`src/sections/\` — one component per nav item
|
|
870
935
|
|
|
871
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
|
|
872
960
|
`;
|
|
873
961
|
}
|
|
874
962
|
function agentsMd(opts) {
|
|
@@ -916,6 +1004,28 @@ This app's declared layer order (in \`index.html\`):
|
|
|
916
1004
|
}
|
|
917
1005
|
\`\`\`
|
|
918
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
|
+
|
|
919
1029
|
More: cascivo's machine-readable guide is at https://cascivo.com/llms.txt.
|
|
920
1030
|
`;
|
|
921
1031
|
}
|
|
@@ -943,6 +1053,14 @@ function buildScaffold(opts) {
|
|
|
943
1053
|
path: "eslint.config.js",
|
|
944
1054
|
contents: eslintConfig()
|
|
945
1055
|
},
|
|
1056
|
+
{
|
|
1057
|
+
path: ".prettierrc",
|
|
1058
|
+
contents: prettierrc()
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
path: ".prettierignore",
|
|
1062
|
+
contents: prettierIgnore()
|
|
1063
|
+
},
|
|
946
1064
|
{
|
|
947
1065
|
path: ".gitignore",
|
|
948
1066
|
contents: gitignore()
|
|
@@ -965,7 +1083,11 @@ function buildScaffold(opts) {
|
|
|
965
1083
|
},
|
|
966
1084
|
{
|
|
967
1085
|
path: "src/App.tsx",
|
|
968
|
-
contents: appTsx(
|
|
1086
|
+
contents: appTsx(sections)
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
path: "src/Shell.tsx",
|
|
1090
|
+
contents: shellTsx(opts)
|
|
969
1091
|
},
|
|
970
1092
|
...sections.map((s) => ({
|
|
971
1093
|
path: `src/sections/${s.component}.tsx`,
|
|
@@ -995,7 +1117,10 @@ async function create(args, cwd = process.cwd()) {
|
|
|
995
1117
|
process.exitCode = 1;
|
|
996
1118
|
return;
|
|
997
1119
|
}
|
|
998
|
-
const pm = detectPackageManager(cwd,
|
|
1120
|
+
const pm = detectPackageManager(cwd, {
|
|
1121
|
+
preferLockfileOverUserAgent: true,
|
|
1122
|
+
...pmFlag.pm ? { override: pmFlag.pm } : {}
|
|
1123
|
+
});
|
|
999
1124
|
const rl = !yes && stdin.isTTY ? createInterface({
|
|
1000
1125
|
input: stdin,
|
|
1001
1126
|
output: stdout
|
|
@@ -1028,7 +1153,7 @@ async function create(args, cwd = process.cwd()) {
|
|
|
1028
1153
|
const templateSpec = flagValue(args, "template");
|
|
1029
1154
|
if (templateSpec) {
|
|
1030
1155
|
const { add } = await Promise.resolve().then(() => add_exports);
|
|
1031
|
-
const { loadConfig } = await import("./config-
|
|
1156
|
+
const { loadConfig } = await import("./config-C8D_CmKI.mjs").then((n) => n.i);
|
|
1032
1157
|
console.log(`\nInstalling template "${templateSpec}"…`);
|
|
1033
1158
|
await add([templateSpec], await loadConfig(), {
|
|
1034
1159
|
cwd: targetDir,
|
|
@@ -1039,6 +1164,13 @@ async function create(args, cwd = process.cwd()) {
|
|
|
1039
1164
|
console.log(` cd ${name}`);
|
|
1040
1165
|
console.log(` ${installAllCommand(pm)}`);
|
|
1041
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");
|
|
1042
1174
|
} finally {
|
|
1043
1175
|
rl?.close();
|
|
1044
1176
|
}
|