cascivo 0.6.2 → 0.7.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
|
@@ -490,9 +490,35 @@ function resolvePackageManagerFlag(args) {
|
|
|
490
490
|
return { error: `Unknown package manager "${raw}". Use one of: pnpm, yarn, npm, bun.` };
|
|
491
491
|
}
|
|
492
492
|
//#endregion
|
|
493
|
+
//#region src/generated/versions.ts
|
|
494
|
+
/**
|
|
495
|
+
* Exact published versions, baked in at build time.
|
|
496
|
+
*
|
|
497
|
+
* Scaffolded apps pin these rather than `"latest"`: the cascivo packages version
|
|
498
|
+
* independently on 0.x, so a floating specifier can resolve a mutually incompatible set,
|
|
499
|
+
* and GETTING-STARTED.md tells adopters to pin exactly. Regenerate with `pnpm regen`.
|
|
500
|
+
*/
|
|
501
|
+
const CASCIVO_VERSIONS = {
|
|
502
|
+
"@cascivo/react": "0.16.0",
|
|
503
|
+
"@cascivo/themes": "0.4.10",
|
|
504
|
+
"@cascivo/charts": "0.16.0",
|
|
505
|
+
"@cascivo/icons": "0.3.7",
|
|
506
|
+
"@cascivo/eslint-config": "0.2.1"
|
|
507
|
+
};
|
|
508
|
+
/** `@cascivo/core`'s declared `@preact/signals-react` peer range. */
|
|
509
|
+
const SIGNALS_PEER = ">=3.0.0";
|
|
510
|
+
//#endregion
|
|
493
511
|
//#region src/commands/create.ts
|
|
494
|
-
/**
|
|
495
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Exact published versions, baked in at build time by `scripts/registry/cli-versions.ts`.
|
|
514
|
+
*
|
|
515
|
+
* This used to be the literal `'latest'` for every cascivo dependency — the loosest
|
|
516
|
+
* possible specifier on a set of independently-versioned 0.x packages, and the direct
|
|
517
|
+
* opposite of GETTING-STARTED.md's "pin **exact** versions (no `^`)". A scaffold that
|
|
518
|
+
* contradicts the docs on the very first file an adopter opens undermines every other rule
|
|
519
|
+
* those docs state, so the pins are generated rather than hand-written.
|
|
520
|
+
*/
|
|
521
|
+
const V = CASCIVO_VERSIONS;
|
|
496
522
|
/** Install-everything command for a package manager (`pnpm install`, `yarn`, …). */
|
|
497
523
|
function installAllCommand(pm) {
|
|
498
524
|
return pm === "yarn" ? "yarn" : `${pm} install`;
|
|
@@ -510,6 +536,24 @@ function pascalCase(label) {
|
|
|
510
536
|
const safe = label.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("") || "Section";
|
|
511
537
|
return /^[0-9]/.test(safe) ? `Section${safe}` : safe;
|
|
512
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Short, human-readable brand for the shell header.
|
|
541
|
+
*
|
|
542
|
+
* The directory name is the wrong thing to render verbatim: a dated demo directory
|
|
543
|
+
* (`vercel-dashboard-2026-07-30-take2`) produced a 45-character brand in the top-left of
|
|
544
|
+
* every page. Take the leading words, title-case them, and stop — a brand is a label, not a
|
|
545
|
+
* slug. Bare numbers and `v2`-style segments are dropped rather than counted.
|
|
546
|
+
*/
|
|
547
|
+
function brandName(name) {
|
|
548
|
+
const words = name.trim().split(/[^a-zA-Z0-9]+/).filter((w) => w !== "" && !/^\d+$/.test(w) && !/^v\d+$/i.test(w));
|
|
549
|
+
const kept = [];
|
|
550
|
+
for (const word of words) {
|
|
551
|
+
if (kept.length >= 3) break;
|
|
552
|
+
if (kept.length > 0 && kept.join(" ").length + 1 + word.length > 24) break;
|
|
553
|
+
kept.push(word.charAt(0).toUpperCase() + word.slice(1));
|
|
554
|
+
}
|
|
555
|
+
return kept.join(" ") || "App";
|
|
556
|
+
}
|
|
513
557
|
/** Normalize the project name into a valid npm package name. */
|
|
514
558
|
function packageName(name) {
|
|
515
559
|
return name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "cascivo-app";
|
|
@@ -553,20 +597,25 @@ function packageJson(opts) {
|
|
|
553
597
|
scripts: {
|
|
554
598
|
dev: "vite",
|
|
555
599
|
build: "tsc && vite build",
|
|
556
|
-
preview: "vite preview"
|
|
600
|
+
preview: "vite preview",
|
|
601
|
+
typecheck: "tsc --noEmit",
|
|
602
|
+
lint: "eslint ."
|
|
557
603
|
},
|
|
558
604
|
dependencies: {
|
|
559
|
-
"@cascivo/
|
|
560
|
-
"@cascivo/
|
|
561
|
-
"@
|
|
562
|
-
"@cascivo/tokens": CASCIVO_DEP,
|
|
605
|
+
"@cascivo/react": V["@cascivo/react"],
|
|
606
|
+
"@cascivo/themes": V["@cascivo/themes"],
|
|
607
|
+
"@preact/signals-react": SIGNALS_PEER,
|
|
563
608
|
react: "^19.0.0",
|
|
564
609
|
"react-dom": "^19.0.0"
|
|
565
610
|
},
|
|
566
611
|
devDependencies: {
|
|
612
|
+
"@cascivo/eslint-config": V["@cascivo/eslint-config"],
|
|
613
|
+
"@eslint/js": "^9.0.0",
|
|
567
614
|
"@types/react": "^19.0.0",
|
|
568
615
|
"@types/react-dom": "^19.0.0",
|
|
569
616
|
"@vitejs/plugin-react": "^5.0.0",
|
|
617
|
+
eslint: "^9.0.0",
|
|
618
|
+
"eslint-plugin-react-hooks": "^7.0.0",
|
|
570
619
|
typescript: "^5.7.0",
|
|
571
620
|
vite: "^7.0.0"
|
|
572
621
|
}
|
|
@@ -617,7 +666,13 @@ function indexHtml(opts) {
|
|
|
617
666
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
618
667
|
<title>${opts.name}</title>
|
|
619
668
|
<style>
|
|
620
|
-
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
|
|
669
|
+
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
|
|
670
|
+
cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
|
|
671
|
+
/* cascivo.example is this app's own slot — above the component/blocks layers so your
|
|
672
|
+
styles win, below cascivo.override which stays free for one-off hotfixes. The
|
|
673
|
+
generated AGENTS.md tells the agent to write there, and this statement is what makes
|
|
674
|
+
that legal: a layer used but never declared falls to the end of the cascade and
|
|
675
|
+
beats everything, which is the opposite of what the ordering is for. */
|
|
621
676
|
/* Third-party CSS goes in the low-priority vendor layer so it can't beat cascivo:
|
|
622
677
|
@import url('some-lib/styles.css') layer(vendor); — see docs/THIRD-PARTY-CSS.md */
|
|
623
678
|
@layer cascivo.reset {
|
|
@@ -674,11 +729,16 @@ function appTsx(opts, sections) {
|
|
|
674
729
|
},`).join("\n");
|
|
675
730
|
const renderedSections = sections.map((s) => ` {section.value === '${s.key}' && <${s.component} />}`).join("\n");
|
|
676
731
|
return `'use client'
|
|
677
|
-
import {
|
|
678
|
-
|
|
732
|
+
import {
|
|
733
|
+
AppShell,
|
|
734
|
+
ShellHeader,
|
|
735
|
+
SideNav,
|
|
736
|
+
signal,
|
|
737
|
+
useSignals,
|
|
738
|
+
type SideNavItem,
|
|
739
|
+
} from '@cascivo/react'
|
|
679
740
|
${sectionImports}
|
|
680
741
|
|
|
681
|
-
import '@cascivo/tokens'
|
|
682
742
|
import '@cascivo/themes/${opts.theme}.css'
|
|
683
743
|
import '@cascivo/react/styles.css'
|
|
684
744
|
|
|
@@ -695,7 +755,7 @@ ${navItems}
|
|
|
695
755
|
|
|
696
756
|
return (
|
|
697
757
|
<AppShell
|
|
698
|
-
header={<ShellHeader brand={{ name: '${opts.name.replace(/'/g, "\\'")}' }} />}
|
|
758
|
+
header={<ShellHeader brand={{ name: '${brandName(opts.name).replace(/'/g, "\\'")}' }} />}
|
|
699
759
|
nav={<SideNav items={navItems} />}
|
|
700
760
|
>
|
|
701
761
|
${renderedSections}
|
|
@@ -740,16 +800,29 @@ export function ${section.component}() {
|
|
|
740
800
|
}
|
|
741
801
|
`;
|
|
742
802
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
803
|
+
/**
|
|
804
|
+
* ESLint flat config for the scaffold.
|
|
805
|
+
*
|
|
806
|
+
* `@cascivo/eslint-config` is wired in from the start because
|
|
807
|
+
* `eslint-plugin-react-hooks@7`'s `recommended-latest` reports every `signal.value = next`
|
|
808
|
+
* — the idiom AI-RULES.md mandates and the generated `App.tsx` below uses — as
|
|
809
|
+
* `Error: This value cannot be modified`. Without this, `pnpm lint` on a freshly scaffolded
|
|
810
|
+
* app errors on every piece of state it ships with.
|
|
811
|
+
*/
|
|
812
|
+
function eslintConfig() {
|
|
813
|
+
return `import js from '@eslint/js'
|
|
814
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
815
|
+
import cascivo from '@cascivo/eslint-config'
|
|
751
816
|
|
|
752
|
-
export default
|
|
817
|
+
export default [
|
|
818
|
+
js.configs.recommended,
|
|
819
|
+
reactHooks.configs['recommended-latest'],
|
|
820
|
+
// Spread LAST — flat config is last-wins. This turns off \`react-hooks/immutability\`,
|
|
821
|
+
// which reports cascivo's signal writes (\`signal.value = next\`) as errors.
|
|
822
|
+
// See https://cascivo.com/docs/using-with-strict-eslint.md
|
|
823
|
+
...cascivo,
|
|
824
|
+
{ ignores: ['dist/**'] },
|
|
825
|
+
]
|
|
753
826
|
`;
|
|
754
827
|
}
|
|
755
828
|
function gitignore() {
|
|
@@ -807,7 +880,7 @@ This app's declared layer order (in \`index.html\`):
|
|
|
807
880
|
|
|
808
881
|
\`\`\`css
|
|
809
882
|
@layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
|
|
810
|
-
cascivo.theme, cascivo.blocks, cascivo.override;
|
|
883
|
+
cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
|
|
811
884
|
\`\`\`
|
|
812
885
|
|
|
813
886
|
### Worked example — nesting, not new layers
|
|
@@ -850,8 +923,8 @@ function buildScaffold(opts) {
|
|
|
850
923
|
contents: indexHtml(opts)
|
|
851
924
|
},
|
|
852
925
|
{
|
|
853
|
-
path: "
|
|
854
|
-
contents:
|
|
926
|
+
path: "eslint.config.js",
|
|
927
|
+
contents: eslintConfig()
|
|
855
928
|
},
|
|
856
929
|
{
|
|
857
930
|
path: ".gitignore",
|
|
@@ -956,20 +1029,35 @@ async function create(args, cwd = process.cwd()) {
|
|
|
956
1029
|
//#endregion
|
|
957
1030
|
//#region src/commands/doctor.ts
|
|
958
1031
|
var doctor_exports = /* @__PURE__ */ __exportAll({
|
|
1032
|
+
checkDuplicateCore: () => checkDuplicateCore,
|
|
959
1033
|
checkProjectDependencies: () => checkProjectDependencies,
|
|
960
1034
|
checkSignalsCompat: () => checkSignalsCompat,
|
|
961
1035
|
checkSsrConfig: () => checkSsrConfig,
|
|
962
|
-
|
|
1036
|
+
detectInstallPath: () => detectInstallPath,
|
|
963
1037
|
runDoctor: () => runDoctor,
|
|
964
1038
|
stripCommentsAndStrings: () => stripCommentsAndStrings
|
|
965
1039
|
});
|
|
966
|
-
/** Runtime packages copied cascivo source needs; the last is @cascivo/core's peer. */
|
|
1040
|
+
/** Runtime packages **copied** cascivo source needs; the last is @cascivo/core's peer. */
|
|
967
1041
|
const REQUIRED_RUNTIME_DEPS = [
|
|
968
1042
|
"@cascivo/core",
|
|
969
1043
|
"@cascivo/tokens",
|
|
970
1044
|
"@cascivo/themes",
|
|
971
1045
|
"@preact/signals-react"
|
|
972
1046
|
];
|
|
1047
|
+
/** Runtime packages a **prebuilt** (Path B) app needs. */
|
|
1048
|
+
const REQUIRED_PREBUILT_DEPS = [
|
|
1049
|
+
"@cascivo/react",
|
|
1050
|
+
"@cascivo/themes",
|
|
1051
|
+
"@preact/signals-react"
|
|
1052
|
+
];
|
|
1053
|
+
/**
|
|
1054
|
+
* Packages a prebuilt app must NOT declare directly, with the rule each one breaks. They
|
|
1055
|
+
* are transitive there, and everything they export is re-exported from `@cascivo/react`.
|
|
1056
|
+
*/
|
|
1057
|
+
const FORBIDDEN_PREBUILT_DEPS = {
|
|
1058
|
+
"@cascivo/core": "AI-RULES.md: never add @cascivo/core to a prebuilt-path app — it is transitive, and everything is re-exported from @cascivo/react",
|
|
1059
|
+
"@cascivo/tokens": "GETTING-STARTED.md: @cascivo/tokens comes with @cascivo/themes as a direct dependency — never install it by hand"
|
|
1060
|
+
};
|
|
973
1061
|
/** Installed on demand by `cascivo add` when a component/chart declares them. */
|
|
974
1062
|
const ON_DEMAND_DEPS = ["@cascivo/i18n", "@cascivo/charts"];
|
|
975
1063
|
const CONFIG_FILES = [
|
|
@@ -977,17 +1065,74 @@ const CONFIG_FILES = [
|
|
|
977
1065
|
"cascivo.config.js",
|
|
978
1066
|
"cascivo.config.mjs"
|
|
979
1067
|
];
|
|
980
|
-
/**
|
|
981
|
-
|
|
982
|
-
|
|
1068
|
+
/**
|
|
1069
|
+
* Infer the install path from evidence.
|
|
1070
|
+
*
|
|
1071
|
+
* This used to be `isAdopterProject()` — "does a `cascivo.config.*` exist?" — and
|
|
1072
|
+
* `cascivo create` wrote that config into every scaffold, including prebuilt-path ones. So
|
|
1073
|
+
* every scaffolded app was judged copy-paste and told to install `@cascivo/core` and
|
|
1074
|
+
* `@cascivo/tokens`, which the docs explicitly forbid on that path. `doctor --ci` exited 1
|
|
1075
|
+
* on a correctly-installed app, which made the CI gate the docs recommend
|
|
1076
|
+
* (`cascivo doctor --ci && cascivo audit --ai src`) red on day one.
|
|
1077
|
+
*
|
|
1078
|
+
* A config file now only contributes evidence when it points at a directory that actually
|
|
1079
|
+
* contains copied source, which is what it was ever meant to signal.
|
|
1080
|
+
*/
|
|
1081
|
+
function detectInstallPath(cwd) {
|
|
1082
|
+
let deps = {};
|
|
1083
|
+
let hasPackageJson = true;
|
|
1084
|
+
try {
|
|
1085
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
1086
|
+
deps = {
|
|
1087
|
+
...pkg.dependencies,
|
|
1088
|
+
...pkg.devDependencies
|
|
1089
|
+
};
|
|
1090
|
+
} catch {
|
|
1091
|
+
hasPackageJson = false;
|
|
1092
|
+
}
|
|
1093
|
+
if (!hasPackageJson) return "unknown";
|
|
1094
|
+
const usesPackage = deps["@cascivo/react"] !== void 0;
|
|
1095
|
+
const copied = hasCopiedSource(cwd);
|
|
1096
|
+
if (usesPackage && copied) return "hybrid";
|
|
1097
|
+
if (usesPackage) return "prebuilt";
|
|
1098
|
+
if (copied) return "copied";
|
|
1099
|
+
return "unknown";
|
|
1100
|
+
}
|
|
1101
|
+
/** Whether the configured output directory holds vendored component source. */
|
|
1102
|
+
function hasCopiedSource(cwd) {
|
|
1103
|
+
for (const dir of outputDirCandidates(cwd)) {
|
|
1104
|
+
const full = join(cwd, dir);
|
|
1105
|
+
if (!existsSync(full)) continue;
|
|
1106
|
+
try {
|
|
1107
|
+
if (readdirSync(full).some((f) => f.endsWith(".tsx"))) return true;
|
|
1108
|
+
} catch {}
|
|
1109
|
+
}
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
/** `outputDir` from the config if it declares one, plus the documented default. */
|
|
1113
|
+
function outputDirCandidates(cwd) {
|
|
1114
|
+
const dirs = new Set(["src/components/ui"]);
|
|
1115
|
+
for (const file of CONFIG_FILES) {
|
|
1116
|
+
const full = join(cwd, file);
|
|
1117
|
+
if (!existsSync(full)) continue;
|
|
1118
|
+
try {
|
|
1119
|
+
const declared = /outputDir\s*:\s*['"]([^'"]+)['"]/.exec(readFileSync(full, "utf8"))?.[1];
|
|
1120
|
+
if (declared !== void 0) dirs.add(declared);
|
|
1121
|
+
} catch {}
|
|
1122
|
+
}
|
|
1123
|
+
return [...dirs];
|
|
983
1124
|
}
|
|
984
1125
|
/**
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
1126
|
+
* Check that the runtime dependencies this project's install path needs are declared in its
|
|
1127
|
+
* package.json — and, on the prebuilt path, that it declares none it must not.
|
|
1128
|
+
*
|
|
1129
|
+
* Turns the opaque "cannot find module '@preact/signals-react'" build failure into a
|
|
1130
|
+
* diagnosed condition with a fix. What it demands depends on `detectInstallPath`: requiring
|
|
1131
|
+
* `@cascivo/core` of a prebuilt app is not merely unhelpful, it is the opposite of what the
|
|
1132
|
+
* docs say, and following the advice makes a correct project wrong.
|
|
1133
|
+
*
|
|
1134
|
+
* `@cascivo/i18n`/`@cascivo/charts` stay advisory since not every project uses them, and an
|
|
1135
|
+
* `unknown` path emits nothing at all rather than guessing.
|
|
991
1136
|
*/
|
|
992
1137
|
function checkProjectDependencies(cwd) {
|
|
993
1138
|
let deps = {};
|
|
@@ -1000,13 +1145,24 @@ function checkProjectDependencies(cwd) {
|
|
|
1000
1145
|
} catch {
|
|
1001
1146
|
return [];
|
|
1002
1147
|
}
|
|
1148
|
+
const path = detectInstallPath(cwd);
|
|
1149
|
+
if (path === "unknown") return [];
|
|
1003
1150
|
const pm = detectPackageManager(cwd);
|
|
1004
1151
|
const findings = [];
|
|
1005
|
-
|
|
1152
|
+
const required = path === "prebuilt" ? REQUIRED_PREBUILT_DEPS : REQUIRED_RUNTIME_DEPS;
|
|
1153
|
+
for (const pkg of required) if (deps[pkg] === void 0) findings.push({
|
|
1006
1154
|
package: pkg,
|
|
1007
1155
|
required: true,
|
|
1008
|
-
hint: installHint(pm, [pkg])
|
|
1156
|
+
hint: `detected ${path} install path — ${installHint(pm, [pkg])}`
|
|
1009
1157
|
});
|
|
1158
|
+
if (path === "prebuilt") {
|
|
1159
|
+
for (const [pkg, reason] of Object.entries(FORBIDDEN_PREBUILT_DEPS)) if (deps[pkg] !== void 0) findings.push({
|
|
1160
|
+
package: pkg,
|
|
1161
|
+
required: true,
|
|
1162
|
+
kind: "forbidden",
|
|
1163
|
+
hint: reason
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1010
1166
|
for (const pkg of ON_DEMAND_DEPS) if (deps[pkg] === void 0) findings.push({
|
|
1011
1167
|
package: pkg,
|
|
1012
1168
|
required: false,
|
|
@@ -1014,6 +1170,29 @@ function checkProjectDependencies(cwd) {
|
|
|
1014
1170
|
});
|
|
1015
1171
|
return findings;
|
|
1016
1172
|
}
|
|
1173
|
+
/** Packages that carry their own `@cascivo/core` dependency. */
|
|
1174
|
+
const CORE_DEPENDENTS = [
|
|
1175
|
+
"@cascivo/react",
|
|
1176
|
+
"@cascivo/charts",
|
|
1177
|
+
"@cascivo/flow",
|
|
1178
|
+
"@cascivo/editor"
|
|
1179
|
+
];
|
|
1180
|
+
async function checkDuplicateCore(cwd) {
|
|
1181
|
+
const root = await readInstalledPackageVersion(cwd, "@cascivo/core");
|
|
1182
|
+
const nested = [];
|
|
1183
|
+
for (const owner of CORE_DEPENDENTS) {
|
|
1184
|
+
const ownerRoot = join(cwd, "node_modules", owner);
|
|
1185
|
+
if (!existsSync(ownerRoot)) continue;
|
|
1186
|
+
const version = await readInstalledPackageVersion(ownerRoot, "@cascivo/core");
|
|
1187
|
+
if (version !== null && version !== root) nested.push(`${owner} → ${version}`);
|
|
1188
|
+
}
|
|
1189
|
+
if (nested.length === 0) return null;
|
|
1190
|
+
return {
|
|
1191
|
+
root,
|
|
1192
|
+
nested,
|
|
1193
|
+
hint: "Align the @cascivo/* versions (they are released together — see breaking-changes.json) and reinstall. Two copies of @cascivo/core means two signal registries: writes through one are invisible to components subscribed through the other, with no error."
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1017
1196
|
/**
|
|
1018
1197
|
* Checks the installed `@preact/signals-react` against the installed React.
|
|
1019
1198
|
* React 19 removed the `__SECRET_INTERNALS…` export that signals-react 2.x
|
|
@@ -2523,27 +2702,29 @@ async function run(args) {
|
|
|
2523
2702
|
} else {
|
|
2524
2703
|
const cwd = process.cwd();
|
|
2525
2704
|
const result = await runDoctor(cwd);
|
|
2526
|
-
const { checkProjectDependencies, checkSignalsCompat, checkSsrConfig,
|
|
2527
|
-
const adopter =
|
|
2705
|
+
const { checkDuplicateCore, checkProjectDependencies, checkSignalsCompat, checkSsrConfig, detectInstallPath } = await Promise.resolve().then(() => doctor_exports);
|
|
2706
|
+
const adopter = detectInstallPath(cwd) !== "unknown";
|
|
2528
2707
|
const deps = adopter ? checkProjectDependencies(cwd) : [];
|
|
2529
2708
|
const missingRequired = deps.filter((d) => d.required);
|
|
2530
2709
|
const signalsCompat = adopter ? await checkSignalsCompat(cwd) : null;
|
|
2531
2710
|
const signalsError = signalsCompat?.severity === "error";
|
|
2532
2711
|
const ssrHint = adopter ? checkSsrConfig(cwd) : null;
|
|
2533
|
-
|
|
2712
|
+
const duplicateCore = adopter ? await checkDuplicateCore(cwd) : null;
|
|
2713
|
+
if (result.passed && deps.length === 0 && signalsCompat === null && ssrHint === null && duplicateCore === null) console.log("No violations found.");
|
|
2534
2714
|
else {
|
|
2535
2715
|
for (const v of result.violations) console.error(`[${v.rule}] ${v.detail}\n ${v.file}`);
|
|
2536
|
-
for (const d of missingRequired) console.error(`[
|
|
2716
|
+
for (const d of missingRequired) console.error(d.kind === "forbidden" ? `[forbidden-dependency] ${d.package} must not be a direct dependency here. Remove it — ${d.hint}` : `[missing-dependency] ${d.package} is not in package.json. Install: ${d.hint}`);
|
|
2537
2717
|
if (signalsCompat) (signalsError ? console.error : console.log)(`[${signalsError ? "signals-incompatible" : "signals-outdated"}] ${signalsCompat.detail} Upgrade: ${signalsCompat.hint}`);
|
|
2538
2718
|
if (ssrHint) console.log(`[ssr-config] ${ssrHint}`);
|
|
2719
|
+
if (duplicateCore) console.error(`[duplicate-core] More than one @cascivo/core is installed (root: ${duplicateCore.root ?? "none"}; ${duplicateCore.nested.join("; ")}). ` + duplicateCore.hint);
|
|
2539
2720
|
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}`);
|
|
2540
|
-
if (ci && (result.violations.length > 0 || missingRequired.length > 0 || signalsError)) process.exitCode = 1;
|
|
2721
|
+
if (ci && (result.violations.length > 0 || missingRequired.length > 0 || signalsError || duplicateCore !== null)) process.exitCode = 1;
|
|
2541
2722
|
}
|
|
2542
2723
|
}
|
|
2543
2724
|
break;
|
|
2544
2725
|
}
|
|
2545
2726
|
case "audit": {
|
|
2546
|
-
const { audit } = await import("./audit-
|
|
2727
|
+
const { audit } = await import("./audit-C4ul-2Ix.mjs");
|
|
2547
2728
|
await audit(rest, await loadConfig());
|
|
2548
2729
|
break;
|
|
2549
2730
|
}
|