cascivo 0.7.1 → 0.7.2
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/{config-C6GdrbvF.mjs → config-D7ddWN_9.mjs} +27 -4
- package/dist/{config-C6GdrbvF.mjs.map → config-D7ddWN_9.mjs.map} +1 -1
- package/dist/{drift-qTzJ75Ds.mjs → drift-B02BbFN_.mjs} +22 -15
- package/dist/drift-B02BbFN_.mjs.map +1 -0
- package/dist/generated/audit-contract.json +73 -1
- package/dist/index.mjs +191 -23
- package/dist/index.mjs.map +1 -1
- package/dist/lock-CW8UuEPJ.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/drift-qTzJ75Ds.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock-CW8UuEPJ.mjs","names":[],"sources":["../src/utils/registry.ts","../src/utils/lock.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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lock-CW8UuEPJ.mjs","names":[],"sources":["../src/utils/registry.ts","../src/utils/lock.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 /** Present when the component is deprecated — rendered by `list`, `search` and `add`. */\n deprecated?: { since: string; replacement: string; note?: 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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cascivo",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI — npx cascivo init / add / list / update",
|
|
6
6
|
"keywords": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"provenance": true
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@cascivo/registry": "^0.2.
|
|
46
|
+
"@cascivo/registry": "^0.2.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^25",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"drift-qTzJ75Ds.mjs","names":[],"sources":["../src/commands/drift.ts"],"sourcesContent":["import type { CascadeConfig } from '../utils/config.js'\nimport { readFileSafe } from '../utils/fs.js'\nimport { readLock, sha256 } from '../utils/lock.js'\nimport { checkPeerVersions } from '../utils/peer-versions.js'\nimport { fetchRegistry, findComponent } from '../utils/registry.js'\n\n/**\n * `cascivo doctor --drift` — compares installed components against the\n * registry. Two drift classes:\n *\n * 1. Local-edit drift: an installed file's content no longer matches what\n * was copied at install time (hand edits, or deleted after install).\n * 2. Peer-version drift: the currently-registered component source needs a\n * newer `@cascivo/*` peer package (per `peerVersions`) than what's\n * actually installed in node_modules — the dashboard-feedback failure\n * mode (DataTable referencing an i18n builtin key an older published\n * @cascivo/i18n build doesn't have).\n */\nexport async function runDoctorDrift(\n config: CascadeConfig,\n cwd: string = process.cwd(),\n): Promise<void> {\n const lock = await readLock(cwd)\n if (!lock || Object.keys(lock.items).length === 0) {\n console.log('No installed components found in cascivo.lock.')\n return\n }\n\n const registry = await fetchRegistry(config.registry)\n let driftCount = 0\n\n for (const [name, entry] of Object.entries(lock.items)) {\n const current = findComponent(registry, name)\n if (!current) continue\n\n for (const [path, lockedHash] of Object.entries(entry.files)) {\n const content = await readFileSafe(path)\n if (content === null) {\n console.log(`${name}: ${path} is missing (installed, then deleted)`)\n driftCount++\n continue\n }\n if (sha256(content) !== lockedHash) {\n console.log(`${name}: ${path} has local edits (differs from the version installed)`)\n driftCount++\n }\n }\n\n if (current.peerVersions) {\n const violations = await checkPeerVersions(cwd, current.peerVersions)\n for (const v of violations) {\n const installedDesc = v.installed ? `${v.installed} is installed` : 'it is not installed'\n console.log(`${name}: needs ${v.pkg} ${v.required}, but ${installedDesc}.`)\n driftCount++\n }\n }\n }\n\n if (driftCount > 0) {\n console.log(`\\n${driftCount} drift issue(s) found.`)\n process.exitCode = 1\n } else {\n console.log('No drift detected — installed components match the registry.')\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,eAAsB,eACpB,QACA,MAAc,QAAQ,IAAI,GACX;CACf,MAAM,OAAO,MAAM,SAAS,GAAG;CAC/B,IAAI,CAAC,QAAQ,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG;EACjD,QAAQ,IAAI,gDAAgD;EAC5D;CACF;CAEA,MAAM,WAAW,MAAM,cAAc,OAAO,QAAQ;CACpD,IAAI,aAAa;CAEjB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,KAAK,GAAG;EACtD,MAAM,UAAU,cAAc,UAAU,IAAI;EAC5C,IAAI,CAAC,SAAS;EAEd,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,MAAM,KAAK,GAAG;GAC5D,MAAM,UAAU,MAAM,aAAa,IAAI;GACvC,IAAI,YAAY,MAAM;IACpB,QAAQ,IAAI,GAAG,KAAK,IAAI,KAAK,sCAAsC;IACnE;IACA;GACF;GACA,IAAI,OAAO,OAAO,MAAM,YAAY;IAClC,QAAQ,IAAI,GAAG,KAAK,IAAI,KAAK,sDAAsD;IACnF;GACF;EACF;EAEA,IAAI,QAAQ,cAAc;GACxB,MAAM,aAAa,MAAM,kBAAkB,KAAK,QAAQ,YAAY;GACpE,KAAK,MAAM,KAAK,YAAY;IAC1B,MAAM,gBAAgB,EAAE,YAAY,GAAG,EAAE,UAAU,iBAAiB;IACpE,QAAQ,IAAI,GAAG,KAAK,UAAU,EAAE,IAAI,GAAG,EAAE,SAAS,QAAQ,cAAc,EAAE;IAC1E;GACF;EACF;CACF;CAEA,IAAI,aAAa,GAAG;EAClB,QAAQ,IAAI,KAAK,WAAW,uBAAuB;EACnD,QAAQ,WAAW;CACrB,OACE,QAAQ,IAAI,8DAA8D;AAE9E"}
|