Make Camino examples and scaffolds complete Yarn and Nix packages
This commit is contained in:
Vendored
+1
File diff suppressed because one or more lines are too long
+13
-8
@@ -1,3 +1,4 @@
|
||||
import { installDependencies } from "./install-dependencies.mjs";
|
||||
import { buildService } from "./build-service.mjs";
|
||||
import { buildComponents } from "./build-components.mjs";
|
||||
/** Trusted Nix build driver. No package-defined build hooks run between generation and witnessing. */
|
||||
@@ -69,14 +70,18 @@ for (const { target, mode, settings } of builds) {
|
||||
await writable(work);
|
||||
const modules = path.join(work, "node_modules");
|
||||
await fs.mkdir(path.join(modules, "@quixos"), { recursive: true });
|
||||
if (config.nodeModules) {
|
||||
for (const item of await fs.readdir(config.nodeModules, { withFileTypes: true })) {
|
||||
if (item.name === "@quixos") throw Error("Application dependencies cannot substitute the checked SDK");
|
||||
await fs.symlink(path.join(config.nodeModules, item.name), path.join(modules, item.name));
|
||||
}
|
||||
}
|
||||
await fs.symlink(config.core, path.join(modules, "@quixos/camino-replica-core"));
|
||||
await fs.writeFile(path.join(work, "package.json"), JSON.stringify({ type: "module" }));
|
||||
await installDependencies({
|
||||
destination: modules,
|
||||
application: config.nodeModules,
|
||||
selected: { "@quixos/camino-replica-core": config.core },
|
||||
reserved: ["@quixos/camino-replica-core", "@quixos/camino-replica-client", "@quixos/camino-replica-engine"],
|
||||
});
|
||||
const packageFile = path.join(work, "package.json");
|
||||
const packageMetadata = await fs.readFile(packageFile, "utf8").then(JSON.parse, (error) => {
|
||||
if (error.code === "ENOENT") return {};
|
||||
throw error;
|
||||
});
|
||||
await fs.writeFile(packageFile, JSON.stringify({ ...packageMetadata, type: "module" }));
|
||||
const bindingPath = relative(settings.bindingOutput);
|
||||
const source =
|
||||
mode === "remote"
|
||||
|
||||
+38
-11
@@ -1,3 +1,4 @@
|
||||
import { installDependencies } from "./install-dependencies.mjs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -66,17 +67,43 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
|
||||
await writable(work);
|
||||
const modules = path.join(work, "node_modules");
|
||||
await fs.mkdir(path.join(modules, "@quixos"), { recursive: true });
|
||||
if (config.nodeModules)
|
||||
for (const item of await fs.readdir(config.nodeModules, { withFileTypes: true })) {
|
||||
if (["@quixos", "react", "react-dom", "scheduler", "@types", "csstype"].includes(item.name))
|
||||
throw Error("Application dependencies cannot substitute selected React/client types");
|
||||
await fs.symlink(path.join(config.nodeModules, item.name), path.join(modules, item.name));
|
||||
}
|
||||
for (const item of await fs.readdir(path.join(settings.dependencies, "node_modules"))) {
|
||||
await fs.symlink(path.join(settings.dependencies, "node_modules", item), path.join(modules, item));
|
||||
await installDependencies({
|
||||
destination: modules,
|
||||
application: config.nodeModules,
|
||||
defaults: [path.join(settings.dependencies, "node_modules")],
|
||||
selected: { "@quixos/camino-react": settings.sdk },
|
||||
reserved: [
|
||||
"react",
|
||||
"react-dom",
|
||||
"scheduler",
|
||||
"csstype",
|
||||
"@types/react",
|
||||
"@types/react-dom",
|
||||
"@types/prop-types",
|
||||
"@quixos/camino-react",
|
||||
"@quixos/camino-replica-core",
|
||||
"@quixos/camino-replica-client",
|
||||
"@quixos/camino-replica-engine",
|
||||
],
|
||||
});
|
||||
const packageFile = path.join(work, "package.json");
|
||||
const packageMetadata = await fs.readFile(packageFile, "utf8").then(JSON.parse, (error) => {
|
||||
if (error.code === "ENOENT") return {};
|
||||
throw error;
|
||||
});
|
||||
await fs.writeFile(packageFile, JSON.stringify({ ...packageMetadata, type: "module" }));
|
||||
// CommonJS dependencies may require React. An ESM bridge lets esbuild lower
|
||||
// that require while retaining the one host-owned runtime instance.
|
||||
const sharedAliases = [];
|
||||
for (const [name, url] of Object.entries(imports)) {
|
||||
const shim = path.join(work, "shared-" + digest(Buffer.from(name)).slice(0, 16) + ".mjs");
|
||||
await fs.writeFile(
|
||||
shim,
|
||||
`export * from ${JSON.stringify(url)};` +
|
||||
(name === "@quixos/camino-react" ? "" : `export {default} from ${JSON.stringify(url)};`),
|
||||
);
|
||||
sharedAliases.push("--alias:" + name + "=" + shim, "--external:" + url);
|
||||
}
|
||||
await fs.symlink(settings.sdk, path.join(modules, "@quixos/camino-react"));
|
||||
await fs.writeFile(path.join(work, "package.json"), JSON.stringify({ type: "module" }));
|
||||
const bindingPath = compiler.artifactPath(settings.bindingOutput),
|
||||
bindings = compiler.generateComponentClientBindings(plan, pkg.revision),
|
||||
assets = compiler.componentAssetDeclarations();
|
||||
@@ -187,7 +214,7 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
|
||||
'--define:process.env.NODE_ENV="production"',
|
||||
"--metafile=" + metadata,
|
||||
"--outfile=" + path.join(output, directory, "module.mjs"),
|
||||
...Object.entries(imports).flatMap(([name, url]) => ["--alias:" + name + "=" + url, "--external:" + url]),
|
||||
...sharedAliases,
|
||||
...["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"].map(
|
||||
(extension) => "--loader:." + extension + "=dataurl",
|
||||
),
|
||||
|
||||
@@ -23,7 +23,12 @@ const writable = async (directory) => {
|
||||
}
|
||||
};
|
||||
await writable(work);
|
||||
await fs.writeFile(path.join(work, "package.json"), JSON.stringify({ type: "module" }));
|
||||
const packageFile = path.join(work, "package.json");
|
||||
const metadata = await fs.readFile(packageFile, "utf8").then(JSON.parse, (error) => {
|
||||
if (error.code === "ENOENT") return {};
|
||||
throw error;
|
||||
});
|
||||
await fs.writeFile(packageFile, JSON.stringify({ ...metadata, type: "module" }));
|
||||
if (config.dependencies) await fs.symlink(config.dependencies, path.join(work, "node_modules"));
|
||||
const members = Object.create(null);
|
||||
let index = 0;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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();
|
||||
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);
|
||||
}
|
||||
for (const [name, file] of entries) {
|
||||
await fs.mkdir(path.dirname(path.join(destination, name)), { recursive: true });
|
||||
await fs.symlink(file, path.join(destination, name));
|
||||
}
|
||||
}
|
||||
@@ -848,6 +848,8 @@ in
|
||||
{
|
||||
mkCaminoReactPackage = import ./react-package.nix;
|
||||
mkCaminoReactContracts = import ./react-contracts.nix;
|
||||
mkCaminoYarnDependencies = import ./yarn-dependencies.nix;
|
||||
mkCaminoYarnShell = import ./yarn-shell.nix;
|
||||
mkCaminoTypeScriptPackage = import ./typescript-package.nix;
|
||||
mkCaminoTypeScriptService = import ./typescript-service.nix;
|
||||
assembleCaminoPackage = import ./assemble-package.nix;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
pkgs,
|
||||
src,
|
||||
nodejs ? pkgs.nodejs_24,
|
||||
}:
|
||||
let
|
||||
project = (pkgs.callPackage (src + "/yarn-project.nix") { inherit nodejs; }) {
|
||||
inherit src;
|
||||
overrideAttrs = _: {
|
||||
buildPhase = "runHook preBuild; runHook postBuild";
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
test -d node_modules
|
||||
mkdir -p "$out"
|
||||
cp -a node_modules/. "$out/"
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
project
|
||||
// {
|
||||
devShell = pkgs.mkShell {
|
||||
packages = [
|
||||
nodejs
|
||||
project.yarn-freestanding
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ pkgs }:
|
||||
let
|
||||
yarnCli = pkgs.fetchurl {
|
||||
url = "https://repo.yarnpkg.com/4.18.0/packages/yarnpkg-cli/bin/yarn.js";
|
||||
hash = "sha512-/Lhxb+fNDuzhQf/Bi5IZOp35IEwbqDGJwoiDUiP8C75kr0c7qw1emSen2utcryuwfrJ4fMkzjKBA6hJfKh8vfg==";
|
||||
};
|
||||
yarn = pkgs.writeShellScriptBin "yarn" ''
|
||||
exec ${pkgs.nodejs_24}/bin/node ${yarnCli} "$@"
|
||||
'';
|
||||
in
|
||||
pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.nodejs_24
|
||||
yarn
|
||||
];
|
||||
shellHook = ''
|
||||
if [ ! -f .yarn/plugins/yarn-plugin-nixify.cjs ]; then
|
||||
mkdir -p .yarn/plugins
|
||||
cp ${./.yarn/plugins/yarn-plugin-nixify.cjs} .yarn/plugins/yarn-plugin-nixify.cjs
|
||||
fi
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user