cascivo 0.2.0 → 0.3.4

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.
@@ -0,0 +1,244 @@
1
+ import { n as fetchJsonFresh } from "./http-CJZ5W0Fa.mjs";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { readFile, unlink, writeFile } from "node:fs/promises";
5
+ import { createHash } from "node:crypto";
6
+ //#region src/utils/registry.ts
7
+ function asStringArray(value) {
8
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
9
+ }
10
+ /** Validate and normalize raw JSON into a typed Registry. Throws on bad shape. */
11
+ function parseRegistry(raw) {
12
+ if (typeof raw !== "object" || raw === null) throw new Error("Invalid registry: expected an object");
13
+ const obj = raw;
14
+ if (!Array.isArray(obj.components)) throw new Error("Invalid registry: \"components\" must be an array");
15
+ const components = obj.components.map((entry, i) => {
16
+ if (typeof entry !== "object" || entry === null) throw new Error(`Invalid registry: component at index ${i} is not an object`);
17
+ const c = entry;
18
+ if (typeof c.name !== "string") throw new Error(`Invalid registry: component at index ${i} is missing "name"`);
19
+ const rawType = c.type;
20
+ const type = [
21
+ "component",
22
+ "layout",
23
+ "block",
24
+ "chart",
25
+ "section",
26
+ "flow",
27
+ "editor"
28
+ ].includes(rawType) ? rawType : void 0;
29
+ const rawMeta = c.meta;
30
+ const metaName = typeof rawMeta === "object" && rawMeta !== null && typeof rawMeta.name === "string" ? rawMeta.name : c.name;
31
+ const result = {
32
+ name: c.name,
33
+ description: typeof c.description === "string" ? c.description : "",
34
+ category: typeof c.category === "string" ? c.category : "",
35
+ version: typeof c.version === "string" ? c.version : "0.0.0",
36
+ files: asStringArray(c.files),
37
+ dependencies: asStringArray(c.dependencies),
38
+ tags: asStringArray(c.tags),
39
+ meta: { name: metaName }
40
+ };
41
+ if (type !== void 0) result.type = type;
42
+ if (typeof c.install === "string") result.install = c.install;
43
+ if (typeof c.styles === "string") result.styles = c.styles;
44
+ if (Array.isArray(c.registryDependencies)) {
45
+ const deps = asStringArray(c.registryDependencies);
46
+ if (deps.length > 0) result.registryDependencies = deps;
47
+ }
48
+ if (typeof c.fileHashes === "object" && c.fileHashes !== null) {
49
+ const hashes = {};
50
+ for (const [file, hash] of Object.entries(c.fileHashes)) if (typeof hash === "string") hashes[file] = hash;
51
+ if (Object.keys(hashes).length > 0) result.fileHashes = hashes;
52
+ }
53
+ if (typeof c.peerVersions === "object" && c.peerVersions !== null) {
54
+ const floors = {};
55
+ for (const [dep, floor] of Object.entries(c.peerVersions)) if (typeof floor === "string") floors[dep] = floor;
56
+ if (Object.keys(floors).length > 0) result.peerVersions = floors;
57
+ }
58
+ return result;
59
+ });
60
+ const blocks = Array.isArray(obj.blocks) ? obj.blocks.flatMap((entry) => {
61
+ if (typeof entry !== "object" || entry === null) return [];
62
+ const b = entry;
63
+ if (typeof b.name !== "string") return [];
64
+ const rawScreenshot = typeof b.screenshot === "object" && b.screenshot !== null ? b.screenshot : {};
65
+ return [{
66
+ name: b.name,
67
+ type: "block",
68
+ displayName: typeof b.displayName === "string" ? b.displayName : b.name,
69
+ description: typeof b.description === "string" ? b.description : "",
70
+ category: typeof b.category === "string" ? b.category : "",
71
+ version: typeof b.version === "string" ? b.version : "0.0.0",
72
+ files: asStringArray(b.files),
73
+ dependencies: asStringArray(b.dependencies),
74
+ tags: asStringArray(b.tags),
75
+ screenshot: {
76
+ light: typeof rawScreenshot.light === "string" ? rawScreenshot.light : "",
77
+ dark: typeof rawScreenshot.dark === "string" ? rawScreenshot.dark : ""
78
+ }
79
+ }];
80
+ }) : [];
81
+ const templates = Array.isArray(obj.templates) ? obj.templates.flatMap((entry) => {
82
+ if (typeof entry !== "object" || entry === null) return [];
83
+ const name = entry.name;
84
+ return typeof name === "string" ? [name] : [];
85
+ }) : [];
86
+ return {
87
+ version: typeof obj.version === "string" ? obj.version : "0.0.0",
88
+ generatedAt: typeof obj.generatedAt === "string" ? obj.generatedAt : "",
89
+ components,
90
+ blocks,
91
+ templates
92
+ };
93
+ }
94
+ /**
95
+ * Fetch and parse the registry from a URL. Retries transient failures and
96
+ * falls back to the last cached copy when offline (via fetchJsonFresh).
97
+ */
98
+ async function fetchRegistry(url) {
99
+ try {
100
+ return parseRegistry(await fetchJsonFresh(url));
101
+ } catch (e) {
102
+ const msg = e instanceof Error ? e.message : String(e);
103
+ throw new Error(`Failed to fetch registry from ${url}: ${msg}`);
104
+ }
105
+ }
106
+ /**
107
+ * Find a component by name (case-insensitive, full name or unambiguous suffix).
108
+ *
109
+ * Examples:
110
+ * "layout/app-shell" → exact match on full name
111
+ * "app-shell" → suffix match if exactly one entry ends with "/app-shell"
112
+ * "button" → exact match (no slash, no suffix ambiguity)
113
+ */
114
+ function findComponent(registry, name) {
115
+ const target = name.toLowerCase();
116
+ if (target.startsWith("block/")) {
117
+ const blockName = target.slice(6);
118
+ const block = (registry.blocks ?? []).find((b) => b.name.toLowerCase() === blockName);
119
+ if (!block) throw new Error(`Block "${blockName}" not found in registry.`);
120
+ return block;
121
+ }
122
+ const exact = registry.components.find((c) => c.name.toLowerCase() === target);
123
+ if (exact) return exact;
124
+ const suffix = `/${target}`;
125
+ const matches = registry.components.filter((c) => c.name.toLowerCase().endsWith(suffix));
126
+ if (matches.length === 1) return matches[0];
127
+ }
128
+ /** Extract the file name from a registry file URL. */
129
+ function fileName(url) {
130
+ const stripped = url.split(/[?#]/)[0] ?? url;
131
+ return stripped.split("/").pop() || stripped;
132
+ }
133
+ //#endregion
134
+ //#region src/utils/lock.ts
135
+ function sha256(content) {
136
+ return `sha256-${createHash("sha256").update(content).digest("hex")}`;
137
+ }
138
+ const LOCK_FILENAME = "cascivo.lock";
139
+ /** Pre-rename lockfile name — read as a fallback, migrated away on next write. */
140
+ const LEGACY_LOCK_FILENAME = "cascade.lock";
141
+ async function readLock(cwd = process.cwd()) {
142
+ for (const filename of [LOCK_FILENAME, LEGACY_LOCK_FILENAME]) {
143
+ const path = join(cwd, filename);
144
+ if (!existsSync(path)) continue;
145
+ try {
146
+ const raw = JSON.parse(await readFile(path, "utf8"));
147
+ if (typeof raw !== "object" || raw === null) return null;
148
+ return raw;
149
+ } catch {
150
+ return null;
151
+ }
152
+ }
153
+ return null;
154
+ }
155
+ async function writeLock(lock, cwd = process.cwd()) {
156
+ await writeFile(join(cwd, LOCK_FILENAME), `${JSON.stringify(lock, null, 2)}\n`, "utf8");
157
+ const legacyPath = join(cwd, LEGACY_LOCK_FILENAME);
158
+ if (existsSync(legacyPath)) try {
159
+ await unlink(legacyPath);
160
+ console.log("Migrated cascade.lock → cascivo.lock");
161
+ } catch {}
162
+ }
163
+ function createLock() {
164
+ return {
165
+ lockVersion: 1,
166
+ items: {}
167
+ };
168
+ }
169
+ function updateLockEntry(lock, name, entry) {
170
+ return {
171
+ ...lock,
172
+ items: {
173
+ ...lock.items,
174
+ [name]: entry
175
+ }
176
+ };
177
+ }
178
+ //#endregion
179
+ //#region src/utils/semver.ts
180
+ /**
181
+ * Minimal semver comparison — just enough to evaluate the `>=x.y.z` floors
182
+ * the registry generator emits for `peerVersions` (scripts/registry/generate.ts).
183
+ * Not a general-purpose range parser; unsupported input is treated as
184
+ * unsatisfied so callers surface it rather than silently pass.
185
+ */
186
+ function parseVersion(v) {
187
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
188
+ if (!m) return null;
189
+ return [
190
+ Number(m[1]),
191
+ Number(m[2]),
192
+ Number(m[3])
193
+ ];
194
+ }
195
+ /** Compares two `x.y.z` versions: -1 if a<b, 0 if equal, 1 if a>b. Throws on unparsable input. */
196
+ function compareVersions(a, b) {
197
+ const pa = parseVersion(a);
198
+ const pb = parseVersion(b);
199
+ if (!pa || !pb) throw new Error(`Cannot compare versions: "${a}" vs "${b}"`);
200
+ for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
201
+ return 0;
202
+ }
203
+ /** Evaluates whether `installed` satisfies a floor spec of the form `>=x.y.z`. */
204
+ function satisfiesFloor(installed, floor) {
205
+ const m = /^>=\s*(\d+\.\d+\.\d+)/.exec(floor.trim());
206
+ if (!m) return true;
207
+ try {
208
+ return compareVersions(installed, m[1]) >= 0;
209
+ } catch {
210
+ return true;
211
+ }
212
+ }
213
+ //#endregion
214
+ //#region src/utils/peer-versions.ts
215
+ /** Reads the installed version of a package from the project's node_modules, or null if absent/unreadable. */
216
+ async function readInstalledPackageVersion(cwd, pkgName) {
217
+ try {
218
+ const pkg = JSON.parse(await readFile(join(cwd, "node_modules", pkgName, "package.json"), "utf8"));
219
+ return typeof pkg.version === "string" ? pkg.version : null;
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+ /**
225
+ * Checks installed peer package versions against the floors a registry entry
226
+ * declares (`peerVersions`, e.g. `{ "@cascivo/i18n": ">=0.2.1" }`). Returns
227
+ * only the packages that fail — either missing or older than required.
228
+ */
229
+ async function checkPeerVersions(cwd, floors) {
230
+ const violations = [];
231
+ for (const [pkg, required] of Object.entries(floors)) {
232
+ const installed = await readInstalledPackageVersion(cwd, pkg);
233
+ if (installed === null || !satisfiesFloor(installed, required)) violations.push({
234
+ pkg,
235
+ required,
236
+ installed
237
+ });
238
+ }
239
+ return violations;
240
+ }
241
+ //#endregion
242
+ export { updateLockEntry as a, fileName as c, sha256 as i, findComponent as l, createLock as n, writeLock as o, readLock as r, fetchRegistry as s, checkPeerVersions as t };
243
+
244
+ //# sourceMappingURL=peer-versions-B2bCgko-.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-versions-B2bCgko-.mjs","names":[],"sources":["../src/utils/registry.ts","../src/utils/lock.ts","../src/utils/semver.ts","../src/utils/peer-versions.ts"],"sourcesContent":["import { fetchJsonFresh } from './http.js'\n\nexport interface RegistryComponent {\n name: string\n type?: 'component' | 'layout' | 'block' | 'chart' | 'section' | 'flow' | 'editor'\n description: string\n category: string\n version: string\n files: string[]\n /** filename → sha256 of upstream content — used by `update --check`. */\n fileHashes?: Record<string, string>\n /** npm package to install (charts/flow/editor). */\n install?: string\n /** stylesheet the npm package requires, e.g. `@cascivo/charts/styles.css`. */\n styles?: string\n dependencies: string[]\n /**\n * Minimum published version required for each `@cascivo/*` dependency, e.g.\n * `{ \"@cascivo/i18n\": \">=0.2.1\" }`. Used by `add`/`doctor --drift` to detect\n * an installed peer package that is older than what this copied source needs.\n */\n peerVersions?: Record<string, string>\n /** Other registry components to install transitively (shared hooks/siblings). */\n registryDependencies?: string[]\n tags: string[]\n meta: { name: string }\n}\n\nexport interface BlockRegistryEntry {\n name: string\n type: 'block'\n displayName: string\n description: string\n category: string\n version: string\n files: string[]\n dependencies: string[]\n tags: string[]\n screenshot: { light: string; dark: string }\n}\n\nexport interface Registry {\n version: string\n generatedAt: string\n components: RegistryComponent[]\n blocks?: BlockRegistryEntry[]\n /** Names of first-party templates — installed via the per-item path (r/<name>.json). */\n templates: string[]\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : []\n}\n\n/** Validate and normalize raw JSON into a typed Registry. Throws on bad shape. */\nexport function parseRegistry(raw: unknown): Registry {\n if (typeof raw !== 'object' || raw === null) {\n throw new Error('Invalid registry: expected an object')\n }\n const obj = raw as Record<string, unknown>\n if (!Array.isArray(obj.components)) {\n throw new Error('Invalid registry: \"components\" must be an array')\n }\n\n const components: RegistryComponent[] = obj.components.map((entry, i) => {\n if (typeof entry !== 'object' || entry === null) {\n throw new Error(`Invalid registry: component at index ${i} is not an object`)\n }\n const c = entry as Record<string, unknown>\n if (typeof c.name !== 'string') {\n throw new Error(`Invalid registry: component at index ${i} is missing \"name\"`)\n }\n const rawType = c.type\n const KNOWN_TYPES = [\n 'component',\n 'layout',\n 'block',\n 'chart',\n 'section',\n 'flow',\n 'editor',\n ] as const\n const type = (KNOWN_TYPES as readonly string[]).includes(rawType as string)\n ? (rawType as RegistryComponent['type'])\n : undefined\n const rawMeta = c.meta\n const metaName =\n typeof rawMeta === 'object' &&\n rawMeta !== null &&\n typeof (rawMeta as Record<string, unknown>).name === 'string'\n ? ((rawMeta as Record<string, unknown>).name as string)\n : c.name\n const result: RegistryComponent = {\n name: c.name,\n description: typeof c.description === 'string' ? c.description : '',\n category: typeof c.category === 'string' ? c.category : '',\n version: typeof c.version === 'string' ? c.version : '0.0.0',\n files: asStringArray(c.files),\n dependencies: asStringArray(c.dependencies),\n tags: asStringArray(c.tags),\n meta: { name: metaName },\n }\n if (type !== undefined) result.type = type\n if (typeof c.install === 'string') result.install = c.install\n if (typeof c.styles === 'string') result.styles = c.styles\n if (Array.isArray(c.registryDependencies)) {\n const deps = asStringArray(c.registryDependencies)\n if (deps.length > 0) result.registryDependencies = deps\n }\n if (typeof c.fileHashes === 'object' && c.fileHashes !== null) {\n const hashes: Record<string, string> = {}\n for (const [file, hash] of Object.entries(c.fileHashes as Record<string, unknown>)) {\n if (typeof hash === 'string') hashes[file] = hash\n }\n if (Object.keys(hashes).length > 0) result.fileHashes = hashes\n }\n if (typeof c.peerVersions === 'object' && c.peerVersions !== null) {\n const floors: Record<string, string> = {}\n for (const [dep, floor] of Object.entries(c.peerVersions as Record<string, unknown>)) {\n if (typeof floor === 'string') floors[dep] = floor\n }\n if (Object.keys(floors).length > 0) result.peerVersions = floors\n }\n return result\n })\n\n const blocks: BlockRegistryEntry[] = Array.isArray(obj.blocks)\n ? obj.blocks.flatMap((entry) => {\n if (typeof entry !== 'object' || entry === null) return []\n const b = entry as Record<string, unknown>\n if (typeof b.name !== 'string') return []\n const rawScreenshot =\n typeof b.screenshot === 'object' && b.screenshot !== null\n ? (b.screenshot as Record<string, unknown>)\n : {}\n return [\n {\n name: b.name,\n type: 'block' as const,\n displayName: typeof b.displayName === 'string' ? b.displayName : b.name,\n description: typeof b.description === 'string' ? b.description : '',\n category: typeof b.category === 'string' ? b.category : '',\n version: typeof b.version === 'string' ? b.version : '0.0.0',\n files: asStringArray(b.files),\n dependencies: asStringArray(b.dependencies),\n tags: asStringArray(b.tags),\n screenshot: {\n light: typeof rawScreenshot.light === 'string' ? rawScreenshot.light : '',\n dark: typeof rawScreenshot.dark === 'string' ? rawScreenshot.dark : '',\n },\n },\n ]\n })\n : []\n\n const templates: string[] = Array.isArray(obj.templates)\n ? obj.templates.flatMap((entry) => {\n if (typeof entry !== 'object' || entry === null) return []\n const name = (entry as Record<string, unknown>).name\n return typeof name === 'string' ? [name] : []\n })\n : []\n\n return {\n version: typeof obj.version === 'string' ? obj.version : '0.0.0',\n generatedAt: typeof obj.generatedAt === 'string' ? obj.generatedAt : '',\n components,\n blocks,\n templates,\n }\n}\n\n/**\n * Fetch and parse the registry from a URL. Retries transient failures and\n * falls back to the last cached copy when offline (via fetchJsonFresh).\n */\nexport async function fetchRegistry(url: string): Promise<Registry> {\n try {\n return parseRegistry(await fetchJsonFresh(url))\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e)\n throw new Error(`Failed to fetch registry from ${url}: ${msg}`)\n }\n}\n\n/**\n * Find a component by name (case-insensitive, full name or unambiguous suffix).\n *\n * Examples:\n * \"layout/app-shell\" → exact match on full name\n * \"app-shell\" → suffix match if exactly one entry ends with \"/app-shell\"\n * \"button\" → exact match (no slash, no suffix ambiguity)\n */\nexport function findComponent(registry: Registry, name: string): RegistryComponent | undefined {\n const target = name.toLowerCase()\n\n // Handle block/ prefix: look up in the blocks array.\n if (target.startsWith('block/')) {\n const blockName = target.slice('block/'.length)\n const block = (registry.blocks ?? []).find((b) => b.name.toLowerCase() === blockName)\n if (!block) throw new Error(`Block \"${blockName}\" not found in registry.`)\n return block as unknown as RegistryComponent\n }\n\n // 1. Exact full-name match (case-insensitive).\n const exact = registry.components.find((c) => c.name.toLowerCase() === target)\n if (exact) return exact\n\n // 2. Suffix match: resolve \"app-shell\" to \"layout/app-shell\" when unambiguous.\n const suffix = `/${target}`\n const matches = registry.components.filter((c) => c.name.toLowerCase().endsWith(suffix))\n if (matches.length === 1) return matches[0]\n\n return undefined\n}\n\n/** Fuzzy-ish search over name, tags and description. */\nexport function searchComponents(registry: Registry, query: string): RegistryComponent[] {\n const q = query.toLowerCase()\n return registry.components.filter(\n (c) =>\n c.name.toLowerCase().includes(q) ||\n c.description.toLowerCase().includes(q) ||\n c.tags.some((t) => t.toLowerCase().includes(q)),\n )\n}\n\n/** Extract the file name from a registry file URL. */\nexport function fileName(url: string): string {\n const stripped = url.split(/[?#]/)[0] ?? url\n return stripped.split('/').pop() || stripped\n}\n","import { createHash } from 'node:crypto'\nimport { existsSync } from 'node:fs'\nimport { readFile, unlink, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nexport interface LockEntry {\n registry: string\n version: string\n installedAt: string\n files: Record<string, string>\n conflicted?: true\n}\n\nexport interface LockFile {\n lockVersion: 1\n items: Record<string, LockEntry>\n}\n\nexport function sha256(content: string): string {\n return `sha256-${createHash('sha256').update(content).digest('hex')}`\n}\n\nconst LOCK_FILENAME = 'cascivo.lock'\n/** Pre-rename lockfile name — read as a fallback, migrated away on next write. */\nconst LEGACY_LOCK_FILENAME = 'cascade.lock'\n\nexport async function readLock(cwd: string = process.cwd()): Promise<LockFile | null> {\n for (const filename of [LOCK_FILENAME, LEGACY_LOCK_FILENAME]) {\n const path = join(cwd, filename)\n if (!existsSync(path)) continue\n try {\n const raw = JSON.parse(await readFile(path, 'utf8')) as unknown\n if (typeof raw !== 'object' || raw === null) return null\n return raw as LockFile\n } catch {\n return null\n }\n }\n return null\n}\n\nexport async function writeLock(lock: LockFile, cwd: string = process.cwd()): Promise<void> {\n const path = join(cwd, LOCK_FILENAME)\n await writeFile(path, `${JSON.stringify(lock, null, 2)}\\n`, 'utf8')\n // Migrate away from the legacy name on the first write (read-only commands\n // like --dry-run never touch it).\n const legacyPath = join(cwd, LEGACY_LOCK_FILENAME)\n if (existsSync(legacyPath)) {\n try {\n await unlink(legacyPath)\n console.log('Migrated cascade.lock → cascivo.lock')\n } catch {\n // Leaving the legacy file behind is harmless; cascivo.lock now wins.\n }\n }\n}\n\nexport function createLock(): LockFile {\n return { lockVersion: 1, items: {} }\n}\n\nexport function updateLockEntry(lock: LockFile, name: string, entry: LockEntry): LockFile {\n return {\n ...lock,\n items: { ...lock.items, [name]: entry },\n }\n}\n","/**\n * Minimal semver comparison — just enough to evaluate the `>=x.y.z` floors\n * the registry generator emits for `peerVersions` (scripts/registry/generate.ts).\n * Not a general-purpose range parser; unsupported input is treated as\n * unsatisfied so callers surface it rather than silently pass.\n */\n\nfunction parseVersion(v: string): [number, number, number] | null {\n const m = /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(v.trim())\n if (!m) return null\n return [Number(m[1]), Number(m[2]), Number(m[3])]\n}\n\n/** Compares two `x.y.z` versions: -1 if a<b, 0 if equal, 1 if a>b. Throws on unparsable input. */\nexport function compareVersions(a: string, b: string): number {\n const pa = parseVersion(a)\n const pb = parseVersion(b)\n if (!pa || !pb) throw new Error(`Cannot compare versions: \"${a}\" vs \"${b}\"`)\n for (let i = 0; i < 3; i++) {\n if (pa[i] !== pb[i]) return pa[i]! < pb[i]! ? -1 : 1\n }\n return 0\n}\n\n/** Evaluates whether `installed` satisfies a floor spec of the form `>=x.y.z`. */\nexport function satisfiesFloor(installed: string, floor: string): boolean {\n const m = /^>=\\s*(\\d+\\.\\d+\\.\\d+)/.exec(floor.trim())\n if (!m) return true // unrecognized floor shape — don't block on it\n try {\n return compareVersions(installed, m[1]!) >= 0\n } catch {\n return true // unparsable installed version — don't block on it\n }\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { satisfiesFloor } from './semver.js'\n\n/** Reads the installed version of a package from the project's node_modules, or null if absent/unreadable. */\nexport async function readInstalledPackageVersion(\n cwd: string,\n pkgName: string,\n): Promise<string | null> {\n try {\n const pkg = JSON.parse(\n await readFile(join(cwd, 'node_modules', pkgName, 'package.json'), 'utf8'),\n ) as { version?: string }\n return typeof pkg.version === 'string' ? pkg.version : null\n } catch {\n return null\n }\n}\n\nexport interface PeerVersionViolation {\n pkg: string\n required: string\n installed: string | null\n}\n\n/**\n * Checks installed peer package versions against the floors a registry entry\n * declares (`peerVersions`, e.g. `{ \"@cascivo/i18n\": \">=0.2.1\" }`). Returns\n * only the packages that fail — either missing or older than required.\n */\nexport async function checkPeerVersions(\n cwd: string,\n floors: Record<string, string>,\n): Promise<PeerVersionViolation[]> {\n const violations: PeerVersionViolation[] = []\n for (const [pkg, required] of Object.entries(floors)) {\n const installed = await readInstalledPackageVersion(cwd, pkg)\n if (installed === null || !satisfiesFloor(installed, required)) {\n violations.push({ pkg, required, installed })\n }\n }\n return violations\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { r as writeFileSafe } from "./fs-m7ZvuBBm.mjs";
2
- import { join, resolve } from "node:path";
3
2
  import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
4
  import { readFile } from "node:fs/promises";
5
5
  //#region src/commands/template-init.ts
6
6
  function pascal(name) {
@@ -176,4 +176,4 @@ async function templateInit(args, cwd = process.cwd()) {
176
176
  //#endregion
177
177
  export { templateInit };
178
178
 
179
- //# sourceMappingURL=template-init-DGEgatij.mjs.map
179
+ //# sourceMappingURL=template-init-C46fRXo-.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"template-init-DGEgatij.mjs","names":[],"sources":["../src/commands/template-init.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\nimport type { RegistryItem, TemplateMeta } from '@cascivo/registry'\nimport { writeFileSafe } from '../utils/fs.js'\n\nexport interface TemplateInitOptions {\n /** kebab-case template name (also the source folder + registry item name). */\n name: string\n /** Discovery category, e.g. \"dashboard\". */\n category: string\n /** Target framework for the page source. */\n framework: TemplateMeta['framework']\n /** Components the template composes (registry names). */\n components: string[]\n /** Repo owner/name used to build raw file URLs and the homepage. */\n repo?: string\n /** License SPDX id. */\n license: string\n /** Author. */\n author?: string\n}\n\nexport interface ScaffoldFile {\n /** Path relative to the template repo root. */\n path: string\n contents: string\n}\n\nfunction pascal(name: string): string {\n return name\n .split(/[-_/]/)\n .filter(Boolean)\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('')\n}\n\nfunction rawUrl(repo: string | undefined, path: string): string {\n if (!repo) return path\n return `https://raw.githubusercontent.com/${repo}/main/${path}`\n}\n\nfunction pageSource(name: string, components: string[]): string {\n const comp = pascal(name)\n const importLine =\n components.length > 0\n ? `// Components installed via the template's registryDependencies:\\n// ${components.join(', ')}\\n`\n : ''\n return `${importLine}import './${name}.module.css'\n\n/**\n * ${comp} template page. Owned, copy-paste source — adapt freely.\n * Presentational only: no useState/useEffect (cascade house rules).\n */\nexport function ${comp}Page() {\n return (\n <main className=\"${name}-page\">\n <h1>${comp}</h1>\n {/* Compose the template's components here. */}\n </main>\n )\n}\n`\n}\n\nfunction cssSource(name: string): string {\n return `@layer components {\n .${name}-page {\n display: grid;\n gap: var(--cascivo-space-4, 1rem);\n padding: var(--cascivo-space-4, 1rem);\n }\n}\n`\n}\n\nfunction fixturesSource(name: string): string {\n return `/** Mock data for the ${name} template. Replace with your real source. */\nexport const ${name.replace(/-/g, '_')}Fixtures = {\n items: [],\n}\n`\n}\n\nfunction templateMetaSource(meta: TemplateMeta): string {\n return `import type { TemplateMeta } from '@cascivo/registry'\n\nexport const meta: TemplateMeta = ${JSON.stringify(meta, null, 2)}\n`\n}\n\n/**\n * Build the files + registry item for a new template. Pure — no I/O — so it can\n * be unit-tested. The caller writes the files and merges the item into\n * `cascade-registry.json`.\n */\nexport function buildTemplateScaffold(opts: TemplateInitOptions): {\n files: ScaffoldFile[]\n item: RegistryItem\n} {\n const { name, category, framework, components, repo, license, author } = opts\n const dir = `src/${name}`\n const pageTarget = `src/pages/${name}.tsx`\n const cssTarget = `src/pages/${name}.module.css`\n const fixturesTarget = `src/fixtures/${name}.ts`\n\n const files: ScaffoldFile[] = [\n { path: `${dir}/${name}.tsx`, contents: pageSource(name, components) },\n { path: `${dir}/${name}.module.css`, contents: cssSource(name) },\n { path: `${dir}/fixtures.ts`, contents: fixturesSource(name) },\n ]\n\n const meta: TemplateMeta = {\n intent: `${pascal(name)} template`,\n framework,\n category,\n screenshots: [\n { light: rawUrl(repo, `screenshots/${name}-light.png`), alt: `${name} preview (light)` },\n ],\n fileRoles: {\n [pageTarget]: 'page',\n [cssTarget]: 'asset',\n [fixturesTarget]: 'fixture',\n },\n pages: [{ name: pascal(name), target: pageTarget }],\n }\n\n const item: RegistryItem = {\n schemaVersion: 2,\n name,\n type: 'template',\n description: `${pascal(name)} template`,\n category,\n version: '0.1.0',\n license,\n ...(author ? { author } : {}),\n ...(repo ? { homepage: `https://github.com/${repo}` } : {}),\n files: [\n { url: rawUrl(repo, `${dir}/${name}.tsx`), target: pageTarget },\n { url: rawUrl(repo, `${dir}/${name}.module.css`), target: cssTarget },\n { url: rawUrl(repo, `${dir}/fixtures.ts`), target: fixturesTarget },\n ],\n dependencies: [],\n registryDependencies: components,\n tags: [category, 'template'],\n meta,\n }\n\n files.push({ path: `${dir}/${name}.template.ts`, contents: templateMetaSource(meta) })\n\n return { files, item }\n}\n\nfunction flag(args: string[], key: string): string | undefined {\n const eq = args.find((a) => a.startsWith(`--${key}=`))\n if (eq) return eq.slice(key.length + 3)\n const idx = args.indexOf(`--${key}`)\n const next = idx !== -1 ? args[idx + 1] : undefined\n return next && !next.startsWith('--') ? next : undefined\n}\n\nexport async function templateInit(args: string[], cwd: string = process.cwd()): Promise<void> {\n const name = args.find((a) => !a.startsWith('-'))\n if (!name) {\n console.error(\n 'Usage: cascivo template init <name> [--category dashboard] [--framework react-vite] [--components card,line-chart] [--repo owner/repo]',\n )\n process.exitCode = 1\n return\n }\n\n const componentsArg = flag(args, 'components')\n const opts: TemplateInitOptions = {\n name,\n category: flag(args, 'category') ?? 'misc',\n framework: (flag(args, 'framework') as TemplateMeta['framework']) ?? 'react-vite',\n components: componentsArg\n ? componentsArg\n .split(',')\n .map((c) => c.trim())\n .filter(Boolean)\n : [],\n license: flag(args, 'license') ?? 'MIT',\n ...(flag(args, 'repo') ? { repo: flag(args, 'repo')! } : {}),\n ...(flag(args, 'author') ? { author: flag(args, 'author')! } : {}),\n }\n\n const { files, item } = buildTemplateScaffold(opts)\n\n for (const file of files) {\n await writeFileSafe(join(cwd, file.path), file.contents)\n }\n\n // Merge into cascivo-registry.json (create if missing).\n const registryPath = resolve(cwd, 'cascivo-registry.json')\n let index: { schemaVersion: 2; name: string; items: RegistryItem[] }\n if (existsSync(registryPath)) {\n index = JSON.parse(await readFile(registryPath, 'utf8')) as typeof index\n index.items = (index.items ?? []).filter((i) => i.name !== item.name)\n index.items.push(item)\n } else {\n index = { schemaVersion: 2, name: opts.repo ?? name, items: [item] }\n }\n await writeFileSafe(registryPath, `${JSON.stringify(index, null, 2)}\\n`)\n\n console.log(`Scaffolded template \"${name}\" (${files.length} files + cascivo-registry.json).`)\n console.log('Next: fill in the page, capture screenshots, then `cascivo registry build`.')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"template-init-C46fRXo-.mjs","names":[],"sources":["../src/commands/template-init.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join, resolve } from 'node:path'\nimport type { RegistryItem, TemplateMeta } from '@cascivo/registry'\nimport { writeFileSafe } from '../utils/fs.js'\n\nexport interface TemplateInitOptions {\n /** kebab-case template name (also the source folder + registry item name). */\n name: string\n /** Discovery category, e.g. \"dashboard\". */\n category: string\n /** Target framework for the page source. */\n framework: TemplateMeta['framework']\n /** Components the template composes (registry names). */\n components: string[]\n /** Repo owner/name used to build raw file URLs and the homepage. */\n repo?: string\n /** License SPDX id. */\n license: string\n /** Author. */\n author?: string\n}\n\nexport interface ScaffoldFile {\n /** Path relative to the template repo root. */\n path: string\n contents: string\n}\n\nfunction pascal(name: string): string {\n return name\n .split(/[-_/]/)\n .filter(Boolean)\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('')\n}\n\nfunction rawUrl(repo: string | undefined, path: string): string {\n if (!repo) return path\n return `https://raw.githubusercontent.com/${repo}/main/${path}`\n}\n\nfunction pageSource(name: string, components: string[]): string {\n const comp = pascal(name)\n const importLine =\n components.length > 0\n ? `// Components installed via the template's registryDependencies:\\n// ${components.join(', ')}\\n`\n : ''\n return `${importLine}import './${name}.module.css'\n\n/**\n * ${comp} template page. Owned, copy-paste source — adapt freely.\n * Presentational only: no useState/useEffect (cascade house rules).\n */\nexport function ${comp}Page() {\n return (\n <main className=\"${name}-page\">\n <h1>${comp}</h1>\n {/* Compose the template's components here. */}\n </main>\n )\n}\n`\n}\n\nfunction cssSource(name: string): string {\n return `@layer components {\n .${name}-page {\n display: grid;\n gap: var(--cascivo-space-4, 1rem);\n padding: var(--cascivo-space-4, 1rem);\n }\n}\n`\n}\n\nfunction fixturesSource(name: string): string {\n return `/** Mock data for the ${name} template. Replace with your real source. */\nexport const ${name.replace(/-/g, '_')}Fixtures = {\n items: [],\n}\n`\n}\n\nfunction templateMetaSource(meta: TemplateMeta): string {\n return `import type { TemplateMeta } from '@cascivo/registry'\n\nexport const meta: TemplateMeta = ${JSON.stringify(meta, null, 2)}\n`\n}\n\n/**\n * Build the files + registry item for a new template. Pure — no I/O — so it can\n * be unit-tested. The caller writes the files and merges the item into\n * `cascade-registry.json`.\n */\nexport function buildTemplateScaffold(opts: TemplateInitOptions): {\n files: ScaffoldFile[]\n item: RegistryItem\n} {\n const { name, category, framework, components, repo, license, author } = opts\n const dir = `src/${name}`\n const pageTarget = `src/pages/${name}.tsx`\n const cssTarget = `src/pages/${name}.module.css`\n const fixturesTarget = `src/fixtures/${name}.ts`\n\n const files: ScaffoldFile[] = [\n { path: `${dir}/${name}.tsx`, contents: pageSource(name, components) },\n { path: `${dir}/${name}.module.css`, contents: cssSource(name) },\n { path: `${dir}/fixtures.ts`, contents: fixturesSource(name) },\n ]\n\n const meta: TemplateMeta = {\n intent: `${pascal(name)} template`,\n framework,\n category,\n screenshots: [\n { light: rawUrl(repo, `screenshots/${name}-light.png`), alt: `${name} preview (light)` },\n ],\n fileRoles: {\n [pageTarget]: 'page',\n [cssTarget]: 'asset',\n [fixturesTarget]: 'fixture',\n },\n pages: [{ name: pascal(name), target: pageTarget }],\n }\n\n const item: RegistryItem = {\n schemaVersion: 2,\n name,\n type: 'template',\n description: `${pascal(name)} template`,\n category,\n version: '0.1.0',\n license,\n ...(author ? { author } : {}),\n ...(repo ? { homepage: `https://github.com/${repo}` } : {}),\n files: [\n { url: rawUrl(repo, `${dir}/${name}.tsx`), target: pageTarget },\n { url: rawUrl(repo, `${dir}/${name}.module.css`), target: cssTarget },\n { url: rawUrl(repo, `${dir}/fixtures.ts`), target: fixturesTarget },\n ],\n dependencies: [],\n registryDependencies: components,\n tags: [category, 'template'],\n meta,\n }\n\n files.push({ path: `${dir}/${name}.template.ts`, contents: templateMetaSource(meta) })\n\n return { files, item }\n}\n\nfunction flag(args: string[], key: string): string | undefined {\n const eq = args.find((a) => a.startsWith(`--${key}=`))\n if (eq) return eq.slice(key.length + 3)\n const idx = args.indexOf(`--${key}`)\n const next = idx !== -1 ? args[idx + 1] : undefined\n return next && !next.startsWith('--') ? next : undefined\n}\n\nexport async function templateInit(args: string[], cwd: string = process.cwd()): Promise<void> {\n const name = args.find((a) => !a.startsWith('-'))\n if (!name) {\n console.error(\n 'Usage: cascivo template init <name> [--category dashboard] [--framework react-vite] [--components card,line-chart] [--repo owner/repo]',\n )\n process.exitCode = 1\n return\n }\n\n const componentsArg = flag(args, 'components')\n const opts: TemplateInitOptions = {\n name,\n category: flag(args, 'category') ?? 'misc',\n framework: (flag(args, 'framework') as TemplateMeta['framework']) ?? 'react-vite',\n components: componentsArg\n ? componentsArg\n .split(',')\n .map((c) => c.trim())\n .filter(Boolean)\n : [],\n license: flag(args, 'license') ?? 'MIT',\n ...(flag(args, 'repo') ? { repo: flag(args, 'repo')! } : {}),\n ...(flag(args, 'author') ? { author: flag(args, 'author')! } : {}),\n }\n\n const { files, item } = buildTemplateScaffold(opts)\n\n for (const file of files) {\n await writeFileSafe(join(cwd, file.path), file.contents)\n }\n\n // Merge into cascivo-registry.json (create if missing).\n const registryPath = resolve(cwd, 'cascivo-registry.json')\n let index: { schemaVersion: 2; name: string; items: RegistryItem[] }\n if (existsSync(registryPath)) {\n index = JSON.parse(await readFile(registryPath, 'utf8')) as typeof index\n index.items = (index.items ?? []).filter((i) => i.name !== item.name)\n index.items.push(item)\n } else {\n index = { schemaVersion: 2, name: opts.repo ?? name, items: [item] }\n }\n await writeFileSafe(registryPath, `${JSON.stringify(index, null, 2)}\\n`)\n\n console.log(`Scaffolded template \"${name}\" (${files.length} files + cascivo-registry.json).`)\n console.log('Next: fill in the page, capture screenshots, then `cascivo registry build`.')\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cascivo",
3
- "version": "0.2.0",
3
+ "version": "0.3.4",
4
4
  "private": false,
5
5
  "description": "CLI — npx cascivo init / add / list / update",
6
6
  "keywords": [
@@ -42,12 +42,13 @@
42
42
  "provenance": true
43
43
  },
44
44
  "dependencies": {
45
- "@cascivo/registry": "^0.1.5"
45
+ "@cascivo/registry": "^0.1.9"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^25",
49
49
  "typescript": "^5",
50
- "vite-plus": "^0.2.1"
50
+ "vite-plus": "^0.2.1",
51
+ "@cascivo/theme-kit": "0.0.0"
51
52
  },
52
53
  "scripts": {
53
54
  "build": "vp pack && node -e \"const fs=require('fs');const f='dist/index.mjs';const c=fs.readFileSync(f,'utf8');if(!c.startsWith('#!/'))fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c)\" && chmod +x dist/index.mjs",
package/readme.body.md CHANGED
@@ -13,9 +13,18 @@ cascivo view owner/repo/dashboard # preview an item (or template) before instal
13
13
  cascivo list # list available components
14
14
  cascivo update # pull newer versions of copied components
15
15
  cascivo audit --ai # flag hard-coded values, invented props, missing wiring
16
- cascivo build # build a registry from your own components
16
+ cascivo search <query> # search components across registries
17
+ cascivo theme add <name> # install a first-party theme (12 available)
18
+ cascivo eject <component> # eject tokens into a scoped local override file
19
+ cascivo generate <config.json> # generate TSX from a ViewConfig JSON file
20
+ cascivo doctor [--ci] [--drift] # check components for rule violations / registry drift
21
+ cascivo tokens import <file> # import external design tokens as overrides
22
+ cascivo registry build # build a registry from your own components
23
+ cascivo template init <name> # scaffold a new template
17
24
  ```
18
25
 
26
+ Every command supports `--help` for its flags and examples.
27
+
19
28
  A **template** is a registry item (`type: "template"`) that bundles a working page with the components it composes (in `registryDependencies`) and its own page/fixture files (each with a `target`). `cascivo add` installs the component closure into your components directory and writes the template's files to their targets; `create --template` does the same into a freshly scaffolded app.
20
29
 
21
30
  ## How it works
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-CsnCR5eh.mjs","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nexport type ThemeName = 'light' | 'dark' | 'warm'\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: 'https://cascivo.com/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/** Detect the package manager in use from lock files, defaulting to npm. */\nexport function detectPackageManager(cwd: string = process.cwd()): PackageManager {\n if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'\n if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn'\n if (existsSync(join(cwd, 'bun.lockb'))) return 'bun'\n return 'npm'\n}\n\n/** The install subcommand each package manager uses to add dependencies. */\nexport function installCommand(pm: PackageManager, packages: string[]): [string, string[]] {\n const verb = pm === 'npm' ? 'install' : 'add'\n return [pm, [verb, ...packages]]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,iBAAgC;CAI3C,UAAU;CACV,WAAW;CACX,OAAO;AACT;AAEA,MAAM,eAAe;CAAC;CAAqB;CAAqB;AAAoB;;AAGpF,SAAgB,cAAc,SAAmE;CAC/F,OAAO;EAAE,GAAG;EAAgB,GAAG;CAAQ;AACzC;;AAGA,SAAgB,kBACd,QACA,MAAyB,QAAQ,KAClB;CACf,OAAO;EACL,GAAG;EACH,GAAI,IAAI,mBAAmB,EAAE,UAAU,IAAI,iBAAiB,IAAI,CAAC;EACjE,GAAI,IAAI,qBAAqB,EAAE,WAAW,IAAI,mBAAmB,IAAI,CAAC;CACxE;AACF;;;;;;AAOA,eAAsB,WAAW,MAAc,QAAQ,IAAI,GAA2B;CACpF,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,OAAO,KAAK,KAAK,IAAI;EAC3B,IAAI,CAAC,WAAW,IAAI,GAAG;EACvB,IAAI;GACF,MAAM,MAAO,MAAM,OAAO,cAAc,IAAI,CAAC,CAAC;GAG9C,OAAO,kBAAkB,cAAc,IAAI,WAAY,GAA8B,CAAC;EACxF,QAAQ;GAEN,OAAO,kBAAkB,cAAc,IAAI,CAAC;EAC9C;CACF;CACA,OAAO,kBAAkB,cAAc,IAAI,CAAC;AAC9C;;AAKA,SAAgB,qBAAqB,MAAc,QAAQ,IAAI,GAAmB;CAChF,IAAI,WAAW,KAAK,KAAK,gBAAgB,CAAC,GAAG,OAAO;CACpD,IAAI,WAAW,KAAK,KAAK,WAAW,CAAC,GAAG,OAAO;CAC/C,IAAI,WAAW,KAAK,KAAK,WAAW,CAAC,GAAG,OAAO;CAC/C,OAAO;AACT;;AAGA,SAAgB,eAAe,IAAoB,UAAwC;CAEzF,OAAO,CAAC,IAAI,CADC,OAAO,QAAQ,YAAY,OACrB,GAAG,QAAQ,CAAC;AACjC"}
@@ -1,8 +0,0 @@
1
- //#region src/commands/drift.ts
2
- async function runDoctorDrift(_args) {
3
- console.log("cascade doctor --drift: not yet implemented");
4
- }
5
- //#endregion
6
- export { runDoctorDrift };
7
-
8
- //# sourceMappingURL=drift-D7JFzpP_.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"drift-D7JFzpP_.mjs","names":[],"sources":["../src/commands/drift.ts"],"sourcesContent":["export async function runDoctorDrift(_args: string[]): Promise<void> {\n console.log('cascade doctor --drift: not yet implemented')\n}\n"],"mappings":";AAAA,eAAsB,eAAe,OAAgC;CACnE,QAAQ,IAAI,6CAA6C;AAC3D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-CLkdTpxD.mjs","names":[],"sources":["../src/utils/http.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { existsSync } from 'node:fs'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nconst CACHE_DIR = join(homedir(), '.cascade', 'cache')\nconst MAX_SIZE = 10 * 1024 * 1024 // 10 MB\nconst TIMEOUT_MS = 15_000\nconst MAX_RETRIES = 4\n\nexport type FetchFn = (url: string, opts?: RequestInit) => Promise<Response>\n\nlet _fetchFn: FetchFn = globalThis.fetch as FetchFn\n\nexport function setFetch(fn: FetchFn): void {\n _fetchFn = fn\n}\n\nexport function resetFetch(): void {\n _fetchFn = globalThis.fetch as FetchFn\n}\n\nfunction cacheKey(url: string): string {\n return createHash('sha256').update(url).digest('hex')\n}\n\nasync function getCached(url: string): Promise<unknown | null> {\n try {\n const file = join(CACHE_DIR, cacheKey(url))\n const data = await readFile(file, 'utf8')\n return JSON.parse(data) as unknown\n } catch {\n return null\n }\n}\n\nasync function setCached(url: string, data: unknown): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n await writeFile(join(CACHE_DIR, cacheKey(url)), JSON.stringify(data))\n } catch {\n // cache write failure is non-fatal\n }\n}\n\nexport async function fetchJson(\n url: string,\n opts: { headers?: Record<string, string>; params?: Record<string, string>; cache?: boolean } = {},\n): Promise<unknown> {\n const fullUrl = opts.params ? `${url}?${new URLSearchParams(opts.params).toString()}` : url\n\n if (opts.cache !== false) {\n const cached = await getCached(fullUrl)\n if (cached !== null) return cached\n }\n\n let lastError: unknown\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n if (attempt > 0) {\n await new Promise((r) => setTimeout(r, 1000 * 2 ** (attempt - 1)))\n }\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)\n let res: Response\n try {\n res = await _fetchFn(fullUrl, {\n headers: { Accept: 'application/json', ...opts.headers },\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (res.status === 401 || res.status === 403 || res.status === 429) {\n let msg = `HTTP ${res.status}`\n try {\n const body = (await res.json()) as Record<string, unknown>\n if (typeof body['message'] === 'string') msg += `: ${body['message']}`\n } catch {\n // ignore parse failure\n }\n throw new Error(msg)\n }\n\n if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${fullUrl}`)\n\n const len = Number(res.headers.get('content-length') ?? 0)\n if (len > MAX_SIZE) throw new Error(`Response too large (${len} bytes) from ${fullUrl}`)\n\n const text = await res.text()\n if (text.length > MAX_SIZE) throw new Error(`Response too large from ${fullUrl}`)\n\n const json = JSON.parse(text) as unknown\n\n if (opts.cache !== false) await setCached(fullUrl, json)\n return json\n } catch (e) {\n lastError = e\n // Don't retry auth/logic errors\n if (e instanceof Error && /HTTP 4\\d\\d/.test(e.message)) break\n }\n }\n\n const msg = lastError instanceof Error ? lastError.message : String(lastError)\n throw new Error(\n `Network error fetching ${fullUrl}: ${msg}\\n(Check your internet connection or registry URL)`,\n )\n}\n\nexport { existsSync }\n"],"mappings":";;;;;AAMA,MAAM,YAAY,KAAK,QAAQ,GAAG,YAAY,OAAO;AACrD,MAAM,WAAW,KAAK,OAAO;AAC7B,MAAM,aAAa;AACnB,MAAM,cAAc;AAIpB,IAAI,WAAoB,WAAW;AAUnC,SAAS,SAAS,KAAqB;CACrC,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK;AACtD;AAEA,eAAe,UAAU,KAAsC;CAC7D,IAAI;EAEF,MAAM,OAAO,MAAM,SADN,KAAK,WAAW,SAAS,GAAG,CACV,GAAG,MAAM;EACxC,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,UAAU,KAAa,MAA8B;CAClE,IAAI;EACF,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;EAC1C,MAAM,UAAU,KAAK,WAAW,SAAS,GAAG,CAAC,GAAG,KAAK,UAAU,IAAI,CAAC;CACtE,QAAQ,CAER;AACF;AAEA,eAAsB,UACpB,KACA,OAA+F,CAAC,GAC9E;CAClB,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,gBAAgB,KAAK,MAAM,CAAC,CAAC,SAAS,MAAM;CAExF,IAAI,KAAK,UAAU,OAAO;EACxB,MAAM,SAAS,MAAM,UAAU,OAAO;EACtC,IAAI,WAAW,MAAM,OAAO;CAC9B;CAEA,IAAI;CACJ,KAAK,IAAI,UAAU,GAAG,UAAU,aAAa,WAAW;EACtD,IAAI,UAAU,GACZ,MAAM,IAAI,SAAS,MAAM,WAAW,GAAG,MAAO,MAAM,UAAU,EAAE,CAAC;EAEnE,IAAI;GACF,MAAM,aAAa,IAAI,gBAAgB;GACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,UAAU;GAC7D,IAAI;GACJ,IAAI;IACF,MAAM,MAAM,SAAS,SAAS;KAC5B,SAAS;MAAE,QAAQ;MAAoB,GAAG,KAAK;KAAQ;KACvD,QAAQ,WAAW;IACrB,CAAC;GACH,UAAU;IACR,aAAa,KAAK;GACpB;GAEA,IAAI,IAAI,WAAW,OAAO,IAAI,WAAW,OAAO,IAAI,WAAW,KAAK;IAClE,IAAI,MAAM,QAAQ,IAAI;IACtB,IAAI;KACF,MAAM,OAAQ,MAAM,IAAI,KAAK;KAC7B,IAAI,OAAO,KAAK,eAAe,UAAU,OAAO,KAAK,KAAK;IAC5D,QAAQ,CAER;IACA,MAAM,IAAI,MAAM,GAAG;GACrB;GAEA,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,MAAM,QAAQ,IAAI,OAAO,YAAY,SAAS;GAErE,MAAM,MAAM,OAAO,IAAI,QAAQ,IAAI,gBAAgB,KAAK,CAAC;GACzD,IAAI,MAAM,UAAU,MAAM,IAAI,MAAM,uBAAuB,IAAI,eAAe,SAAS;GAEvF,MAAM,OAAO,MAAM,IAAI,KAAK;GAC5B,IAAI,KAAK,SAAS,UAAU,MAAM,IAAI,MAAM,2BAA2B,SAAS;GAEhF,MAAM,OAAO,KAAK,MAAM,IAAI;GAE5B,IAAI,KAAK,UAAU,OAAO,MAAM,UAAU,SAAS,IAAI;GACvD,OAAO;EACT,SAAS,GAAG;GACV,YAAY;GAEZ,IAAI,aAAa,SAAS,aAAa,KAAK,EAAE,OAAO,GAAG;EAC1D;CACF;CAEA,MAAM,MAAM,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS;CAC7E,MAAM,IAAI,MACR,0BAA0B,QAAQ,IAAI,IAAI,mDAC5C;AACF"}