@arcote.tech/arc-cli 0.5.6 → 0.5.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-cli",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "CLI tool for Arc framework",
5
5
  "module": "index.ts",
6
6
  "main": "dist/index.js",
@@ -12,12 +12,12 @@
12
12
  "build": "bun build --target=bun ./src/index.ts --outdir=dist --external @arcote.tech/arc --external @arcote.tech/arc-ds --external @arcote.tech/arc-react --external @arcote.tech/platform && chmod +x dist/index.js"
13
13
  },
14
14
  "dependencies": {
15
- "@arcote.tech/arc": "^0.5.6",
16
- "@arcote.tech/arc-ds": "^0.5.6",
17
- "@arcote.tech/arc-react": "^0.5.6",
18
- "@arcote.tech/arc-host": "^0.5.6",
19
- "@arcote.tech/arc-adapter-db-sqlite": "^0.5.6",
20
- "@arcote.tech/platform": "^0.5.6",
15
+ "@arcote.tech/arc": "^0.5.8",
16
+ "@arcote.tech/arc-ds": "^0.5.8",
17
+ "@arcote.tech/arc-react": "^0.5.8",
18
+ "@arcote.tech/arc-host": "^0.5.8",
19
+ "@arcote.tech/arc-adapter-db-sqlite": "^0.5.8",
20
+ "@arcote.tech/platform": "^0.5.8",
21
21
  "@clack/prompts": "^0.9.0",
22
22
  "commander": "^11.1.0",
23
23
  "chokidar": "^3.5.3",
@@ -0,0 +1,79 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ const CACHE_VERSION = 1;
5
+ const CACHE_FILE = ".build-cache.json";
6
+
7
+ export interface CacheEntry {
8
+ inputHash: string;
9
+ outputHash?: string;
10
+ outputHashes?: Record<string, string>;
11
+ }
12
+
13
+ export interface BuildCache {
14
+ version: typeof CACHE_VERSION;
15
+ units: Record<string, CacheEntry>;
16
+ }
17
+
18
+ function emptyCache(): BuildCache {
19
+ return { version: CACHE_VERSION, units: {} };
20
+ }
21
+
22
+ /** Loads cache from .arc/platform/.build-cache.json. Returns empty cache on
23
+ * missing file, parse error, or version mismatch. */
24
+ export function loadBuildCache(arcDir: string): BuildCache {
25
+ const path = join(arcDir, CACHE_FILE);
26
+ if (!existsSync(path)) return emptyCache();
27
+ try {
28
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
29
+ if (raw?.version !== CACHE_VERSION || typeof raw.units !== "object") {
30
+ return emptyCache();
31
+ }
32
+ return raw as BuildCache;
33
+ } catch {
34
+ return emptyCache();
35
+ }
36
+ }
37
+
38
+ export function saveBuildCache(arcDir: string, cache: BuildCache): void {
39
+ mkdirSync(arcDir, { recursive: true });
40
+ writeFileSync(
41
+ join(arcDir, CACHE_FILE),
42
+ JSON.stringify(cache, null, 2),
43
+ );
44
+ }
45
+
46
+ /** Cache hit = inputHash matches AND none of the required output paths are missing. */
47
+ export function isCacheHit(
48
+ cache: BuildCache,
49
+ unitId: string,
50
+ inputHash: string,
51
+ requiredOutputs: readonly string[] = [],
52
+ ): boolean {
53
+ const entry = cache.units[unitId];
54
+ if (!entry || entry.inputHash !== inputHash) return false;
55
+ for (const out of requiredOutputs) {
56
+ if (!existsSync(out)) return false;
57
+ }
58
+ return true;
59
+ }
60
+
61
+ export function updateCache(
62
+ cache: BuildCache,
63
+ unitId: string,
64
+ inputHash: string,
65
+ output: { outputHash?: string; outputHashes?: Record<string, string> } = {},
66
+ ): void {
67
+ cache.units[unitId] = {
68
+ inputHash,
69
+ ...(output.outputHash !== undefined && { outputHash: output.outputHash }),
70
+ ...(output.outputHashes !== undefined && { outputHashes: output.outputHashes }),
71
+ };
72
+ }
73
+
74
+ export function getCachedEntry(
75
+ cache: BuildCache,
76
+ unitId: string,
77
+ ): CacheEntry | undefined {
78
+ return cache.units[unitId];
79
+ }
@@ -0,0 +1,100 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
+ import { join, relative, sep } from "path";
3
+
4
+ /** sha256 hex of bytes/string. Uses Bun.CryptoHasher. */
5
+ export function sha256Hex(bytes: Uint8Array | ArrayBuffer | string): string {
6
+ const hasher = new Bun.CryptoHasher("sha256");
7
+ hasher.update(bytes as any);
8
+ return hasher.digest("hex");
9
+ }
10
+
11
+ /** Hash of concatenated file contents in stable sorted order. */
12
+ export function sha256OfFiles(paths: readonly string[]): string {
13
+ const hasher = new Bun.CryptoHasher("sha256");
14
+ const sorted = [...paths].sort();
15
+ for (const p of sorted) {
16
+ if (!existsSync(p)) continue;
17
+ hasher.update(readFileSync(p));
18
+ hasher.update("\0");
19
+ }
20
+ return hasher.digest("hex");
21
+ }
22
+
23
+ /** Hash of a directory tree — content + relative path, deterministic.
24
+ * filter receives POSIX-style relative paths (e.g. "src/index.ts").
25
+ * Returns false from filter to skip a path; subdirs whose name is filtered
26
+ * out are pruned entirely. */
27
+ export function sha256OfDir(
28
+ dir: string,
29
+ filter?: (relPath: string) => boolean,
30
+ ): string {
31
+ if (!existsSync(dir)) return sha256Hex("");
32
+ const hasher = new Bun.CryptoHasher("sha256");
33
+ const entries: { rel: string; abs: string }[] = [];
34
+
35
+ function walk(absDir: string) {
36
+ for (const entry of readdirSync(absDir, { withFileTypes: true })) {
37
+ const abs = join(absDir, entry.name);
38
+ const rel = relative(dir, abs).split(sep).join("/");
39
+ if (filter && !filter(rel)) continue;
40
+ if (entry.isDirectory()) walk(abs);
41
+ else if (entry.isFile()) entries.push({ rel, abs });
42
+ }
43
+ }
44
+
45
+ walk(dir);
46
+ entries.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
47
+
48
+ for (const { rel, abs } of entries) {
49
+ hasher.update(rel);
50
+ hasher.update("\0");
51
+ hasher.update(readFileSync(abs));
52
+ hasher.update("\0");
53
+ }
54
+ return hasher.digest("hex");
55
+ }
56
+
57
+ /** Deterministic JSON hash — keys sorted recursively. */
58
+ export function sha256OfJson(value: unknown): string {
59
+ return sha256Hex(stableStringify(value));
60
+ }
61
+
62
+ function stableStringify(value: unknown): string {
63
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
64
+ if (Array.isArray(value)) {
65
+ return "[" + value.map(stableStringify).join(",") + "]";
66
+ }
67
+ const obj = value as Record<string, unknown>;
68
+ const keys = Object.keys(obj).sort();
69
+ return (
70
+ "{" +
71
+ keys
72
+ .map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]))
73
+ .join(",") +
74
+ "}"
75
+ );
76
+ }
77
+
78
+ /** Read installed version of a package from node_modules. Returns null if absent. */
79
+ export function readInstalledVersion(
80
+ rootDir: string,
81
+ pkgName: string,
82
+ ): string | null {
83
+ const pkgJson = join(rootDir, "node_modules", pkgName, "package.json");
84
+ if (!existsSync(pkgJson)) return null;
85
+ try {
86
+ return JSON.parse(readFileSync(pkgJson, "utf-8")).version ?? null;
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /** mtimeMs of a file/dir, or 0 if missing. Useful for asset cache keys. */
93
+ export function mtimeOf(path: string): number {
94
+ if (!existsSync(path)) return 0;
95
+ try {
96
+ return statSync(path).mtimeMs;
97
+ } catch {
98
+ return 0;
99
+ }
100
+ }