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 { buildService } from "./build-service.mjs";
|
||||||
import { buildComponents } from "./build-components.mjs";
|
import { buildComponents } from "./build-components.mjs";
|
||||||
/** Trusted Nix build driver. No package-defined build hooks run between generation and witnessing. */
|
/** 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);
|
await writable(work);
|
||||||
const modules = path.join(work, "node_modules");
|
const modules = path.join(work, "node_modules");
|
||||||
await fs.mkdir(path.join(modules, "@quixos"), { recursive: true });
|
await fs.mkdir(path.join(modules, "@quixos"), { recursive: true });
|
||||||
if (config.nodeModules) {
|
await installDependencies({
|
||||||
for (const item of await fs.readdir(config.nodeModules, { withFileTypes: true })) {
|
destination: modules,
|
||||||
if (item.name === "@quixos") throw Error("Application dependencies cannot substitute the checked SDK");
|
application: config.nodeModules,
|
||||||
await fs.symlink(path.join(config.nodeModules, item.name), path.join(modules, item.name));
|
selected: { "@quixos/camino-replica-core": config.core },
|
||||||
}
|
reserved: ["@quixos/camino-replica-core", "@quixos/camino-replica-client", "@quixos/camino-replica-engine"],
|
||||||
}
|
});
|
||||||
await fs.symlink(config.core, path.join(modules, "@quixos/camino-replica-core"));
|
const packageFile = path.join(work, "package.json");
|
||||||
await fs.writeFile(path.join(work, "package.json"), JSON.stringify({ type: "module" }));
|
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 bindingPath = relative(settings.bindingOutput);
|
||||||
const source =
|
const source =
|
||||||
mode === "remote"
|
mode === "remote"
|
||||||
|
|||||||
+38
-11
@@ -1,3 +1,4 @@
|
|||||||
|
import { installDependencies } from "./install-dependencies.mjs";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
@@ -66,17 +67,43 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
|
|||||||
await writable(work);
|
await writable(work);
|
||||||
const modules = path.join(work, "node_modules");
|
const modules = path.join(work, "node_modules");
|
||||||
await fs.mkdir(path.join(modules, "@quixos"), { recursive: true });
|
await fs.mkdir(path.join(modules, "@quixos"), { recursive: true });
|
||||||
if (config.nodeModules)
|
await installDependencies({
|
||||||
for (const item of await fs.readdir(config.nodeModules, { withFileTypes: true })) {
|
destination: modules,
|
||||||
if (["@quixos", "react", "react-dom", "scheduler", "@types", "csstype"].includes(item.name))
|
application: config.nodeModules,
|
||||||
throw Error("Application dependencies cannot substitute selected React/client types");
|
defaults: [path.join(settings.dependencies, "node_modules")],
|
||||||
await fs.symlink(path.join(config.nodeModules, item.name), path.join(modules, item.name));
|
selected: { "@quixos/camino-react": settings.sdk },
|
||||||
}
|
reserved: [
|
||||||
for (const item of await fs.readdir(path.join(settings.dependencies, "node_modules"))) {
|
"react",
|
||||||
await fs.symlink(path.join(settings.dependencies, "node_modules", item), path.join(modules, item));
|
"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),
|
const bindingPath = compiler.artifactPath(settings.bindingOutput),
|
||||||
bindings = compiler.generateComponentClientBindings(plan, pkg.revision),
|
bindings = compiler.generateComponentClientBindings(plan, pkg.revision),
|
||||||
assets = compiler.componentAssetDeclarations();
|
assets = compiler.componentAssetDeclarations();
|
||||||
@@ -187,7 +214,7 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
|
|||||||
'--define:process.env.NODE_ENV="production"',
|
'--define:process.env.NODE_ENV="production"',
|
||||||
"--metafile=" + metadata,
|
"--metafile=" + metadata,
|
||||||
"--outfile=" + path.join(output, directory, "module.mjs"),
|
"--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(
|
...["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"].map(
|
||||||
(extension) => "--loader:." + extension + "=dataurl",
|
(extension) => "--loader:." + extension + "=dataurl",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ const writable = async (directory) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
await writable(work);
|
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"));
|
if (config.dependencies) await fs.symlink(config.dependencies, path.join(work, "node_modules"));
|
||||||
const members = Object.create(null);
|
const members = Object.create(null);
|
||||||
let index = 0;
|
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;
|
mkCaminoReactPackage = import ./react-package.nix;
|
||||||
mkCaminoReactContracts = import ./react-contracts.nix;
|
mkCaminoReactContracts = import ./react-contracts.nix;
|
||||||
|
mkCaminoYarnDependencies = import ./yarn-dependencies.nix;
|
||||||
|
mkCaminoYarnShell = import ./yarn-shell.nix;
|
||||||
mkCaminoTypeScriptPackage = import ./typescript-package.nix;
|
mkCaminoTypeScriptPackage = import ./typescript-package.nix;
|
||||||
mkCaminoTypeScriptService = import ./typescript-service.nix;
|
mkCaminoTypeScriptService = import ./typescript-service.nix;
|
||||||
assembleCaminoPackage = import ./assemble-package.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