cascivo 0.1.6 → 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.
- package/LICENSE +1 -1
- package/README.md +23 -8
- package/dist/{audit-AHr4MDMK.mjs → audit-InQqdY1s.mjs} +8 -8
- package/dist/{audit-AHr4MDMK.mjs.map → audit-InQqdY1s.mjs.map} +1 -1
- package/dist/config-BsyJbMvD.mjs +106 -0
- package/dist/config-BsyJbMvD.mjs.map +1 -0
- package/dist/drift-Ciw5qeEt.mjs +56 -0
- package/dist/drift-Ciw5qeEt.mjs.map +1 -0
- package/dist/{eject-D_mC1lsq.mjs → eject-Br8V7I3m.mjs} +3 -3
- package/dist/{eject-D_mC1lsq.mjs.map → eject-Br8V7I3m.mjs.map} +1 -1
- package/dist/fs-m7ZvuBBm.mjs +24 -0
- package/dist/fs-m7ZvuBBm.mjs.map +1 -0
- package/dist/{http-CLkdTpxD.mjs → http-CJZ5W0Fa.mjs} +56 -5
- package/dist/http-CJZ5W0Fa.mjs.map +1 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1102 -318
- package/dist/index.mjs.map +1 -1
- package/dist/peer-versions-B2bCgko-.mjs +244 -0
- package/dist/peer-versions-B2bCgko-.mjs.map +1 -0
- package/dist/template-init-C46fRXo-.mjs +179 -0
- package/dist/template-init-C46fRXo-.mjs.map +1 -0
- package/package.json +4 -3
- package/readme.body.md +23 -8
- package/dist/drift-D7JFzpP_.mjs +0 -8
- package/dist/drift-D7JFzpP_.mjs.map +0 -1
- package/dist/http-CLkdTpxD.mjs.map +0 -1
|
@@ -3,11 +3,13 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
//#region src/utils/http.ts
|
|
6
|
-
const CACHE_DIR = join(homedir(), ".
|
|
6
|
+
const CACHE_DIR = join(homedir(), ".cascivo", "cache");
|
|
7
7
|
const MAX_SIZE = 10 * 1024 * 1024;
|
|
8
8
|
const TIMEOUT_MS = 15e3;
|
|
9
9
|
const MAX_RETRIES = 4;
|
|
10
|
-
|
|
10
|
+
function currentFetch() {
|
|
11
|
+
return globalThis.fetch;
|
|
12
|
+
}
|
|
11
13
|
function cacheKey(url) {
|
|
12
14
|
return createHash("sha256").update(url).digest("hex");
|
|
13
15
|
}
|
|
@@ -39,7 +41,7 @@ async function fetchJson(url, opts = {}) {
|
|
|
39
41
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
40
42
|
let res;
|
|
41
43
|
try {
|
|
42
|
-
res = await
|
|
44
|
+
res = await currentFetch()(fullUrl, {
|
|
43
45
|
headers: {
|
|
44
46
|
Accept: "application/json",
|
|
45
47
|
...opts.headers
|
|
@@ -73,7 +75,56 @@ async function fetchJson(url, opts = {}) {
|
|
|
73
75
|
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
74
76
|
throw new Error(`Network error fetching ${fullUrl}: ${msg}\n(Check your internet connection or registry URL)`);
|
|
75
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Fetch text with the same retry/backoff/timeout/size handling as fetchJson,
|
|
80
|
+
* but no cache — used for component file payloads, which must be fresh and
|
|
81
|
+
* whose failures must fail the install (never silently truncate).
|
|
82
|
+
*/
|
|
83
|
+
async function fetchTextRetry(url) {
|
|
84
|
+
let lastError;
|
|
85
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
86
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, 1e3 * 2 ** (attempt - 1)));
|
|
87
|
+
try {
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
90
|
+
let res;
|
|
91
|
+
try {
|
|
92
|
+
res = await currentFetch()(url, { signal: controller.signal });
|
|
93
|
+
} finally {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
}
|
|
96
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
97
|
+
const text = await res.text();
|
|
98
|
+
if (text.length > MAX_SIZE) throw new Error(`Response too large from ${url}`);
|
|
99
|
+
return text;
|
|
100
|
+
} catch (e) {
|
|
101
|
+
lastError = e;
|
|
102
|
+
if (e instanceof Error && /HTTP 4\d\d/.test(e.message)) break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
106
|
+
throw new Error(`Network error fetching ${url}: ${msg}\n(Check your internet connection or registry URL)`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Network-first JSON fetch with an offline fallback to the last cached copy.
|
|
110
|
+
* Used for the registry index: a fresh copy when reachable, the cached one
|
|
111
|
+
* (with a notice) when not.
|
|
112
|
+
*/
|
|
113
|
+
async function fetchJsonFresh(url) {
|
|
114
|
+
try {
|
|
115
|
+
const json = JSON.parse(await fetchTextRetry(url));
|
|
116
|
+
await setCached(url, json);
|
|
117
|
+
return json;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
const cached = await getCached(url);
|
|
120
|
+
if (cached !== null) {
|
|
121
|
+
console.warn(`Could not reach ${url} — using the last cached copy.`);
|
|
122
|
+
return cached;
|
|
123
|
+
}
|
|
124
|
+
throw e;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
76
127
|
//#endregion
|
|
77
|
-
export { fetchJson as t };
|
|
128
|
+
export { fetchJsonFresh as n, fetchTextRetry as r, fetchJson as t };
|
|
78
129
|
|
|
79
|
-
//# sourceMappingURL=http-
|
|
130
|
+
//# sourceMappingURL=http-CJZ5W0Fa.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-CJZ5W0Fa.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(), '.cascivo', '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 | null = null\n\nexport function setFetch(fn: FetchFn): void {\n _fetchFn = fn\n}\n\nexport function resetFetch(): void {\n _fetchFn = null\n}\n\n// Resolved lazily so test stubs of globalThis.fetch (vi.stubGlobal) are seen\n// even when they are installed after this module loads.\nfunction currentFetch(): FetchFn {\n return _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 currentFetch()(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\n/**\n * Fetch text with the same retry/backoff/timeout/size handling as fetchJson,\n * but no cache — used for component file payloads, which must be fresh and\n * whose failures must fail the install (never silently truncate).\n */\nexport async function fetchTextRetry(url: string): Promise<string> {\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 currentFetch()(url, { signal: controller.signal })\n } finally {\n clearTimeout(timer)\n }\n if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`)\n const text = await res.text()\n if (text.length > MAX_SIZE) throw new Error(`Response too large from ${url}`)\n return text\n } catch (e) {\n lastError = e\n // Don't retry client errors\n if (e instanceof Error && /HTTP 4\\d\\d/.test(e.message)) break\n }\n }\n const msg = lastError instanceof Error ? lastError.message : String(lastError)\n throw new Error(\n `Network error fetching ${url}: ${msg}\\n(Check your internet connection or registry URL)`,\n )\n}\n\n/**\n * Network-first JSON fetch with an offline fallback to the last cached copy.\n * Used for the registry index: a fresh copy when reachable, the cached one\n * (with a notice) when not.\n */\nexport async function fetchJsonFresh(url: string): Promise<unknown> {\n try {\n const json = JSON.parse(await fetchTextRetry(url)) as unknown\n await setCached(url, json)\n return json\n } catch (e) {\n const cached = await getCached(url)\n if (cached !== null) {\n console.warn(`Could not reach ${url} — using the last cached copy.`)\n return cached\n }\n throw e\n }\n}\n\nexport { existsSync }\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
//#region src/utils/config.d.ts
|
|
2
|
-
|
|
2
|
+
/** All first-party themes shipped by @cascivo/themes (selectable via data-theme). */
|
|
3
|
+
declare const THEMES: readonly ["light", "dark", "warm", "flat", "minimal", "midnight", "pastel", "brutalist", "corporate", "terminal", "cyberpunk", "arcade"];
|
|
4
|
+
type ThemeName = (typeof THEMES)[number];
|
|
3
5
|
type RegistryNamespaceConfig = string | {
|
|
4
6
|
url: string;
|
|
5
7
|
headers?: Record<string, string>;
|
|
@@ -19,7 +21,7 @@ interface CascadeConfig {
|
|
|
19
21
|
}
|
|
20
22
|
//#endregion
|
|
21
23
|
//#region src/index.d.ts
|
|
22
|
-
declare const VERSION
|
|
24
|
+
declare const VERSION: string;
|
|
23
25
|
declare function run(args: string[]): Promise<void>;
|
|
24
26
|
//#endregion
|
|
25
27
|
export { type CascadeConfig, type ThemeName, VERSION, run };
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/utils/config.ts","../src/index.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/utils/config.ts","../src/index.ts"],"mappings":";AA2BA;AAAA,cAfa,MAAA;AAAA,KAeD,SAAA,WAAoB,MAAM;AAAA,KAE1B,uBAAA;EAEN,GAAA;EAAa,OAAA,GAAU,MAAA;EAAwB,MAAA,GAAS,MAAM;AAAA;AAAA,UAEnD,aAAA;EAFX;EAIJ,QAAA;EAJ2B;EAM3B,SAAA;EAN4D;EAQ5D,KAAA,EAAO,SAAA;EAR2D;EAUlE,UAAA,GAAa,MAAA,SAAe,uBAAA;EARA;EAU5B,KAAA;AAAA;;;cCxBW,OAAA;AAAA,iBAmJS,GAAA,CAAI,IAAA,aAAiB,OAAO"}
|