@riddledc/riddle-proof 0.5.4 → 0.5.6
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/lib/workspace-core.mjs +99 -4
- package/package.json +1 -1
- package/runtime/lib/setup.py +49 -5
package/lib/workspace-core.mjs
CHANGED
|
@@ -2,9 +2,11 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import {
|
|
4
4
|
existsSync,
|
|
5
|
+
copyFileSync,
|
|
5
6
|
lstatSync,
|
|
6
7
|
mkdirSync,
|
|
7
8
|
readFileSync,
|
|
9
|
+
renameSync,
|
|
8
10
|
rmSync,
|
|
9
11
|
symlinkSync,
|
|
10
12
|
unlinkSync,
|
|
@@ -276,6 +278,7 @@ export function ensureWorktree({
|
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
const DEPS_MANIFEST = ".workspace-core-deps.json";
|
|
281
|
+
const DEPS_INPUT_FILES = ["package.json", "package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock"];
|
|
279
282
|
|
|
280
283
|
function depsManifestPath(projectDir) {
|
|
281
284
|
return path.join(projectDir, "node_modules", DEPS_MANIFEST);
|
|
@@ -285,7 +288,7 @@ export function computeDependencyFingerprint(projectDir) {
|
|
|
285
288
|
const packageJson = path.join(projectDir, "package.json");
|
|
286
289
|
if (!existsSync(packageJson)) return "";
|
|
287
290
|
const digest = createHash("sha256");
|
|
288
|
-
for (const name of
|
|
291
|
+
for (const name of DEPS_INPUT_FILES) {
|
|
289
292
|
const filePath = path.join(projectDir, name);
|
|
290
293
|
if (!existsSync(filePath)) continue;
|
|
291
294
|
digest.update(name);
|
|
@@ -318,6 +321,93 @@ function writeDepsManifest(projectDir, fingerprint, installCmd) {
|
|
|
318
321
|
writeFileSync(manifestPath, JSON.stringify({ fingerprint, install_cmd: installCmd }, null, 2));
|
|
319
322
|
}
|
|
320
323
|
|
|
324
|
+
function dependencyCacheRoot(projectDir) {
|
|
325
|
+
if (process.env.RIDDLE_PROOF_DISABLE_DEPS_CACHE === "1") return "";
|
|
326
|
+
const configured = (process.env.RIDDLE_PROOF_DEPS_CACHE_ROOT || "").trim();
|
|
327
|
+
if (configured) return path.resolve(configured);
|
|
328
|
+
|
|
329
|
+
const resolved = path.resolve(projectDir);
|
|
330
|
+
const worktreeMarker = `${path.sep}.riddle-proof-worktrees${path.sep}`;
|
|
331
|
+
const worktreeIndex = resolved.indexOf(worktreeMarker);
|
|
332
|
+
if (worktreeIndex >= 0) {
|
|
333
|
+
return path.join(resolved.slice(0, worktreeIndex), ".riddle-proof-deps-cache");
|
|
334
|
+
}
|
|
335
|
+
return path.join(path.dirname(resolved), ".riddle-proof-deps-cache");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function dependencyCacheKey(fingerprint, installCmd) {
|
|
339
|
+
const nodeMajor = (process.versions.node || "").split(".")[0] || "unknown";
|
|
340
|
+
return [
|
|
341
|
+
sanitizeFragment(installCmd, "install"),
|
|
342
|
+
process.platform,
|
|
343
|
+
process.arch,
|
|
344
|
+
`node${sanitizeFragment(nodeMajor, "unknown")}`,
|
|
345
|
+
fingerprint.slice(0, 24),
|
|
346
|
+
].join("-");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function copyDependencyInputs(sourceDir, targetDir) {
|
|
350
|
+
mkdirSync(targetDir, { recursive: true });
|
|
351
|
+
for (const name of DEPS_INPUT_FILES) {
|
|
352
|
+
const sourcePath = path.join(sourceDir, name);
|
|
353
|
+
if (existsSync(sourcePath)) {
|
|
354
|
+
copyFileSync(sourcePath, path.join(targetDir, name));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function linkNodeModules(projectDir, sourceModules) {
|
|
360
|
+
const projectModules = path.join(projectDir, "node_modules");
|
|
361
|
+
removePath(projectModules);
|
|
362
|
+
symlinkSync(sourceModules, projectModules, "dir");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function tryEnsureCachedDeps({ projectDir, fingerprint, installCmd }) {
|
|
366
|
+
const cacheRoot = dependencyCacheRoot(projectDir);
|
|
367
|
+
if (!cacheRoot) return "";
|
|
368
|
+
|
|
369
|
+
const cacheDir = path.join(cacheRoot, dependencyCacheKey(fingerprint, installCmd));
|
|
370
|
+
const cacheModules = path.join(cacheDir, "node_modules");
|
|
371
|
+
const cacheManifest = readDepsManifest(cacheDir);
|
|
372
|
+
if (cacheManifest.fingerprint === fingerprint && cacheManifest.install_cmd === installCmd && existsSync(cacheModules)) {
|
|
373
|
+
linkNodeModules(projectDir, cacheModules);
|
|
374
|
+
return `reused_cache:${cacheDir}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const tempCacheDir = `${cacheDir}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
|
|
378
|
+
try {
|
|
379
|
+
mkdirSync(path.dirname(cacheDir), { recursive: true });
|
|
380
|
+
removePath(tempCacheDir);
|
|
381
|
+
copyDependencyInputs(projectDir, tempCacheDir);
|
|
382
|
+
|
|
383
|
+
const installResult = runSafe(`${installCmd} 2>&1 | tail -5`, tempCacheDir, dependencyInstallTimeoutMs());
|
|
384
|
+
if (!installResult.ok) {
|
|
385
|
+
removePath(tempCacheDir);
|
|
386
|
+
return "";
|
|
387
|
+
}
|
|
388
|
+
writeDepsManifest(tempCacheDir, fingerprint, installCmd);
|
|
389
|
+
|
|
390
|
+
if (!existsSync(cacheDir)) {
|
|
391
|
+
try {
|
|
392
|
+
renameSync(tempCacheDir, cacheDir);
|
|
393
|
+
} catch {
|
|
394
|
+
removePath(tempCacheDir);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
removePath(tempCacheDir);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const finalManifest = readDepsManifest(cacheDir);
|
|
401
|
+
if (finalManifest.fingerprint === fingerprint && finalManifest.install_cmd === installCmd && existsSync(cacheModules)) {
|
|
402
|
+
linkNodeModules(projectDir, cacheModules);
|
|
403
|
+
return `cached:${installCmd}`;
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
removePath(tempCacheDir);
|
|
407
|
+
}
|
|
408
|
+
return "";
|
|
409
|
+
}
|
|
410
|
+
|
|
321
411
|
function dependencyInstallTimeoutMs() {
|
|
322
412
|
const parsed = Number.parseInt(process.env.RIDDLE_PROOF_INSTALL_TIMEOUT_MS || "", 10);
|
|
323
413
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 600000;
|
|
@@ -337,15 +427,17 @@ export function ensureDeps({ projectDir, reuseFrom = "" } = {}) {
|
|
|
337
427
|
const sourceManifest = readDepsManifest(reuseFrom);
|
|
338
428
|
const sourceModules = path.join(reuseFrom, "node_modules");
|
|
339
429
|
if (sourceFingerprint === fingerprint && sourceManifest.fingerprint === fingerprint && existsSync(sourceModules)) {
|
|
340
|
-
|
|
341
|
-
removePath(projectModules);
|
|
342
|
-
symlinkSync(sourceModules, projectModules);
|
|
430
|
+
linkNodeModules(projectDir, sourceModules);
|
|
343
431
|
return `reused_from:${reuseFrom}`;
|
|
344
432
|
}
|
|
345
433
|
}
|
|
346
434
|
|
|
347
435
|
const installCmd = detectInstallCommand(projectDir);
|
|
348
436
|
if (!installCmd) return "no_install_command";
|
|
437
|
+
const cachedStatus = tryEnsureCachedDeps({ projectDir, fingerprint, installCmd });
|
|
438
|
+
if (cachedStatus) return cachedStatus;
|
|
439
|
+
|
|
440
|
+
removePath(path.join(projectDir, "node_modules"));
|
|
349
441
|
const installResult = runSafe(`${installCmd} 2>&1 | tail -5`, projectDir, dependencyInstallTimeoutMs());
|
|
350
442
|
if (!installResult.ok) {
|
|
351
443
|
throw new Error(`dependency install failed in ${projectDir}: ${installResult.output.slice(0, 300)}`);
|
|
@@ -385,6 +477,9 @@ async function main() {
|
|
|
385
477
|
case "ensure-deps":
|
|
386
478
|
ok({ status: ensureDeps(payload) });
|
|
387
479
|
return;
|
|
480
|
+
case "dependency-fingerprint":
|
|
481
|
+
ok({ fingerprint: computeDependencyFingerprint(payload.projectDir) });
|
|
482
|
+
return;
|
|
388
483
|
default:
|
|
389
484
|
throw new Error(`Unsupported command: ${command}`);
|
|
390
485
|
}
|
package/package.json
CHANGED
package/runtime/lib/setup.py
CHANGED
|
@@ -77,6 +77,13 @@ def ensure_deps(project_dir, reuse_from=''):
|
|
|
77
77
|
return result.get('status', '')
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def dependency_fingerprint(project_dir):
|
|
81
|
+
if not project_dir:
|
|
82
|
+
return ''
|
|
83
|
+
result = workspace_core('dependency-fingerprint', {'projectDir': project_dir}, timeout=30)
|
|
84
|
+
return result.get('fingerprint', '') or ''
|
|
85
|
+
|
|
86
|
+
|
|
80
87
|
def record_setup_phase(phase, status='running', summary=''):
|
|
81
88
|
global s
|
|
82
89
|
ts = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
|
@@ -131,6 +138,24 @@ def ensure_deps_phase(phase, project_dir, reuse_from='', summary=''):
|
|
|
131
138
|
return status
|
|
132
139
|
|
|
133
140
|
|
|
141
|
+
def dependencies_match(left_dir, right_dir):
|
|
142
|
+
left = dependency_fingerprint(left_dir)
|
|
143
|
+
right = dependency_fingerprint(right_dir)
|
|
144
|
+
return bool(left and right and left == right)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def compatible_reuse_source(source_dir, target_dirs):
|
|
148
|
+
if not source_dir:
|
|
149
|
+
return ''
|
|
150
|
+
source = dependency_fingerprint(source_dir)
|
|
151
|
+
if not source:
|
|
152
|
+
return ''
|
|
153
|
+
for target_dir in target_dirs:
|
|
154
|
+
if target_dir and dependency_fingerprint(target_dir) == source:
|
|
155
|
+
return source_dir
|
|
156
|
+
return ''
|
|
157
|
+
|
|
158
|
+
|
|
134
159
|
def resolve_worktree_root(repo_dir):
|
|
135
160
|
configured = (s.get('worktree_root') or os.environ.get('RIDDLE_PROOF_WORKTREE_ROOT') or '').strip()
|
|
136
161
|
if configured:
|
|
@@ -401,17 +426,36 @@ print('After worktree: ' + AFTER_DIR + ' (' + AFTER_WORKTREE_BRANCH + ' -> ' + b
|
|
|
401
426
|
apply_repo_profile(AFTER_DIR)
|
|
402
427
|
save_state(s)
|
|
403
428
|
|
|
429
|
+
target_dependency_dirs = [AFTER_DIR]
|
|
430
|
+
if reference in ('before', 'both'):
|
|
431
|
+
target_dependency_dirs.append(BEFORE_DIR)
|
|
432
|
+
|
|
404
433
|
reuse_source = repo_dir if os.path.exists(os.path.join(repo_dir, 'package.json')) else ''
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
434
|
+
shared_reuse_source = compatible_reuse_source(reuse_source, target_dependency_dirs)
|
|
435
|
+
shared_status = ''
|
|
436
|
+
if shared_reuse_source:
|
|
437
|
+
shared_status = ensure_deps_phase('shared_deps', shared_reuse_source, summary='Ensuring shared repository dependencies.')
|
|
438
|
+
if shared_status:
|
|
439
|
+
print('Shared deps status: ' + shared_status)
|
|
440
|
+
elif reuse_source:
|
|
441
|
+
record_setup_phase(
|
|
442
|
+
'shared_deps',
|
|
443
|
+
'completed',
|
|
444
|
+
'skipped: active workspace dependencies differ from proof worktrees',
|
|
445
|
+
)
|
|
446
|
+
print('Shared deps skipped: active workspace dependencies differ from proof worktrees')
|
|
408
447
|
|
|
409
448
|
before_dep_status = ''
|
|
410
449
|
if reference in ('before', 'both'):
|
|
411
|
-
before_dep_status = ensure_deps_phase('before_deps', BEFORE_DIR, reuse_from=
|
|
450
|
+
before_dep_status = ensure_deps_phase('before_deps', BEFORE_DIR, reuse_from=shared_reuse_source, summary='Ensuring before-worktree dependencies.')
|
|
412
451
|
print('Before deps status: ' + before_dep_status)
|
|
413
452
|
|
|
414
|
-
|
|
453
|
+
after_reuse_source = ''
|
|
454
|
+
if before_dep_status and dependencies_match(BEFORE_DIR, AFTER_DIR):
|
|
455
|
+
after_reuse_source = BEFORE_DIR
|
|
456
|
+
elif shared_reuse_source:
|
|
457
|
+
after_reuse_source = shared_reuse_source
|
|
458
|
+
after_dep_status = ensure_deps_phase('after_deps', AFTER_DIR, reuse_from=after_reuse_source, summary='Ensuring after-worktree dependencies.')
|
|
415
459
|
print('After deps status: ' + after_dep_status)
|
|
416
460
|
|
|
417
461
|
# Patch Next.js config in after worktree if needed
|