Make Camino examples and scaffolds complete Yarn and Nix packages
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
async function packages(directory) {
|
||||
const result = new Map();
|
||||
for (const entry of await fs.readdir(directory)) {
|
||||
if (entry.startsWith(".")) continue;
|
||||
if (entry.startsWith("@")) {
|
||||
for (const name of await fs.readdir(path.join(directory, entry)))
|
||||
result.set(`${entry}/${name}`, path.join(directory, entry, name));
|
||||
} else result.set(entry, path.join(directory, entry));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async function packageDigest(directory) {
|
||||
const digest = createHash("sha256");
|
||||
async function visit(relative) {
|
||||
for (const entry of (await fs.readdir(path.join(directory, relative), { withFileTypes: true })).sort((a, b) =>
|
||||
a.name.localeCompare(b.name),
|
||||
)) {
|
||||
if (entry.name === "node_modules") continue;
|
||||
const name = path.join(relative, entry.name),
|
||||
file = path.join(directory, name);
|
||||
if (entry.isDirectory()) await visit(name);
|
||||
else {
|
||||
const bytes = await fs.readFile(file);
|
||||
digest.update(JSON.stringify([name, bytes.length]));
|
||||
digest.update(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
await visit("");
|
||||
return digest.digest("hex");
|
||||
}
|
||||
/** Merge at package granularity: a scope such as @types is not a reserved package. */
|
||||
export async function installDependencies({ destination, application, defaults = [], selected = {}, reserved = [] }) {
|
||||
const entries = new Map();
|
||||
const authored = new Set();
|
||||
for (const root of defaults) for (const [name, file] of await packages(root)) entries.set(name, file);
|
||||
for (const [name, file] of Object.entries(selected)) entries.set(name, file);
|
||||
if (application)
|
||||
for (const [name, file] of await packages(application)) {
|
||||
if (reserved.includes(name)) {
|
||||
const canonical = entries.get(name);
|
||||
if (!canonical || (await packageDigest(file)) !== (await packageDigest(canonical)))
|
||||
throw Error("Application dependencies cannot substitute selected React/client types or checked SDK: " + name);
|
||||
} else {
|
||||
entries.set(name, file);
|
||||
authored.add(name);
|
||||
}
|
||||
}
|
||||
for (const [name, file] of entries) {
|
||||
await fs.mkdir(path.dirname(path.join(destination, name)), { recursive: true });
|
||||
// Application packages resolve siblings from this assembled node_modules.
|
||||
// Keep SDK links canonical: copying those would duplicate branded types and
|
||||
// React declarations from their own retained dependency closures.
|
||||
if (authored.has(name)) await fs.cp(file, path.join(destination, name), { recursive: true, dereference: true });
|
||||
else await fs.symlink(file, path.join(destination, name));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user