Compare commits

..

4 Commits

Author SHA1 Message Date
Timothy J. Aveni 8dde1f6b82 Add Camino TypeScript package helper 2026-07-12 12:21:21 -07:00
Timothy J. Aveni e0874093f2 Drop perl from package prepare shell 2026-06-27 20:33:06 -07:00
Timothy J. Aveni 0b159fcb81 Add perl to package prepare shell 2026-06-27 20:31:20 -07:00
Timothy J. Aveni f33eee054f Initial commit
germanium cutoff
2026-05-24 15:25:38 -07:00
4 changed files with 15 additions and 176 deletions
-8
View File
@@ -1,8 +0,0 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
"sourceCommit": "8484d99fcf78a49341a17597bc62211e5f1807dd",
"sourcePath": "quixos-instance/quixos-nix-helpers",
"exportName": "quixos-nix-helpers",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
}
-36
View File
@@ -1,36 +0,0 @@
import {createRequire} from "node:module";
import path from "node:path";
// Resolve build dependencies from the package's locked node_modules, not from
// the helper's own Nix-store location. This program runs only during the build.
const require = createRequire(path.join(process.cwd(), "package.json"));
const {build} = require("esbuild");
const options = JSON.parse(process.argv[2]);
const platform = new Map([
["react", "/__quixos/platform/react/v18.mjs"],
["react/jsx-runtime", "/__quixos/platform/react-jsx-runtime/v18.mjs"],
["react/jsx-dev-runtime", "/__quixos/platform/react-jsx-dev-runtime/v18.mjs"],
["@quixos/web-studio-react-runtime", "/__quixos/platform/web-studio-react-runtime/v1.mjs"],
]);
await build({...options, plugins: [{name: "quixos-browser-source", setup(api) {
api.onResolve({filter: /\?browser-source$/}, async ({path: specifier, resolveDir}) => {
if (!specifier.startsWith("./") && !specifier.startsWith("../")) throw new Error("Browser source imports must name a relative compiled module");
const resolved = await api.resolve(specifier.slice(0, -"?browser-source".length), {resolveDir, kind: "import-statement"});
if (resolved.errors.length) return {errors: resolved.errors};
return {path: resolved.path, namespace: "browser-source"};
});
api.onLoad({filter: /.*/, namespace: "browser-source"}, async ({path: entry}) => {
const browser = await build({entryPoints: [entry], bundle: true, write: false, outfile: "component.mjs",
format: "esm", platform: "browser", target: "es2022", metafile: true,
plugins: [{name: "quixos-platform", setup(browserApi) {
browserApi.onResolve({filter: /.*/}, ({path: name}) => platform.has(name) ? {path: platform.get(name), external: true} : undefined);
}}]});
const js = browser.outputFiles.find(file => file.path.endsWith(".mjs"));
if (!js) throw new Error("Browser source build produced no JavaScript");
const css = browser.outputFiles.find(file => file.path.endsWith(".css"));
if (css && Object.values(browser.metafile.outputs).some(output => output.exports?.includes("styles"))) throw new Error("Use either imported CSS or an explicit styles export, not both");
if (browser.outputFiles.some(file => !file.path.endsWith(".mjs") && !file.path.endsWith(".css"))) throw new Error("Browser assets must be embedded, not emitted as unserved files");
const source = js.text + (css ? `\nexport const styles = ${JSON.stringify(css.text)};\n` : "");
return {contents: `export default ${JSON.stringify(source)};`, loader: "js", watchFiles: Object.keys(browser.metafile.inputs)};
});
}}]});
-17
View File
@@ -1,17 +0,0 @@
import fs from "node:fs";
import crypto from "node:crypto";
const [schemaPath, packageRevisionId, bindingOutput, generatorPath, output] = process.argv.slice(2);
if (!schemaPath || !packageRevisionId || !bindingOutput || !generatorPath || !output) throw new Error("Missing candidate check receipt inputs");
const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === "object"
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => [key, canonical(entry)])) : value;
const hash = (value) => `sha256:${crypto.createHash("sha256").update(JSON.stringify(canonical(value))).digest("hex")}`;
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
if (!schema.packages.some((entry) => entry.revisionId === packageRevisionId)) throw new Error("Checked binding schema lacks the package");
const generated = fs.readFileSync(bindingOutput, "utf8");
if (generated !== fs.readFileSync(".qx-checked-bindings", "utf8")) throw new Error("Build replaced candidate-generated bindings; its check is not evidence for this candidate");
const receipt = {
schemaVersion: 1, packageRevisionId, success: true, bindingSchema: schema,
bindingSchemaDigest: hash(schema), generatedDigest: hash(generated),
checkerDigest: hash({ generatorPath, compiler: JSON.parse(fs.readFileSync("node_modules/typescript/package.json", "utf8")), lock: fs.readFileSync("yarn.lock", "utf8") }),
};
fs.writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`, { flag: "wx" });
+15 -115
View File
@@ -510,20 +510,6 @@ EOF
'';
};
# Language-neutral, offline schema compilation. Snapshot directories must be
# fixed Nix inputs matching the resource lock's complete dependency closure.
mkQxBindingSchema = { pkgs, protocol, src, repository, commit, resources ? [ ] }:
let
snapshots = pkgs.writeText "qx-binding-snapshots.json" (builtins.toJSON { inherit resources; });
in pkgs.runCommand "qx-binding-schema.json" { } ''
${protocol}/bin/quixos-resource-compile \
--root ${src} --kind package \
--repository ${pkgs.lib.escapeShellArg repository} \
--commit ${pkgs.lib.escapeShellArg commit} \
--checkout-root "$TMPDIR/checkouts" \
--snapshot-map ${snapshots} --snapshot-only true --schema-out "$out" > /dev/null
'';
mkCaminoTsYarnNixifyFlake =
{
inputs,
@@ -535,13 +521,8 @@ EOF
promptName ? null,
nodejsAttr ? "nodejs_24",
buildCommand ? "yarn build",
sourcePortals ? { },
buildEnv ? { },
# An exact, compiler-produced BindingSchema JSON artifact and a backend.
# Other language helpers can consume the same schema with their own generator/runtime.
bindings ? null,
# A dedicated entrypoint calling SDK serveMigration; never start the
# normal package server in the isolated migration execution boundary.
migrationEntrypoint ? null,
nativeBuildInputs ? [ ],
devShellPackages ? [ ],
devShellHook ? "",
@@ -551,8 +532,6 @@ EOF
flake-utils.lib.eachDefaultSystem (
system:
let
outputsFor = candidateBindings:
let
pkgs = import nixpkgs { inherit system; };
lib = pkgs.lib;
nodejs = pkgs.${nodejsAttr};
@@ -582,34 +561,12 @@ EOF
attrs
);
bindingConfig = if candidateBindings != null then candidateBindings
else if bindings == null then null else callOption bindings;
bindingSchema = if bindingConfig == null then null else bindingConfig.schema or (mkQxBindingSchema {
inherit pkgs;
protocol = bindingConfig.generator;
src = packageRoot;
inherit (bindingConfig) repository commit;
resources = bindingConfig.resources or [ ];
});
bindingOutput = if bindingConfig == null then "src/gen/qx.ts" else bindingConfig.output or "src/gen/qx.ts";
authoringCheck = builtins.fromJSON (builtins.readFile "${packageRoot}/quixos.check.json");
bindingOptionsFile = if bindingConfig == null then null else
pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (authoringCheck.options or { }));
bindingCommand = if bindingConfig == null then "" else ''
mkdir -p ${lib.escapeShellArg (dirOf bindingOutput)}
${bindingConfig.generator}/bin/quixos-codegen-ts \
${lib.escapeShellArg (toString bindingSchema)} \
${lib.escapeShellArg bindingConfig.packageRevisionId} \
${lib.escapeShellArg bindingOutput} ${bindingOptionsFile}
${lib.optionalString (installServer != null && descriptorPath != null) ''
${bindingConfig.generator}/bin/quixos-qx package-descriptor ${lib.escapeShellArg (toString bindingSchema)} \
${lib.escapeShellArg bindingConfig.packageRevisionId} > ${lib.escapeShellArg descriptorPath}
''}
'';
generateBindings = pkgs.writeShellApplication {
name = "qx-generate-bindings";
text = bindingCommand;
};
portalLinksFor = attrs:
lib.concatStringsSep "\n" (
lib.mapAttrsToList
(target: source: "ln -sfn ${source} ${lib.escapeShellArg target}")
attrs
);
bundleConfig = if bundle == null then { } else bundle;
bundleOutfile = bundleConfig.outfile or "server.mjs";
@@ -617,32 +574,16 @@ EOF
bundleTarget = bundleConfig.target or "node24";
bundleFormat = bundleConfig.format or "esm";
bundleBanner = bundleConfig.banner or nodeRequireBanner;
bundleAliases = bundleConfig.aliases or {
"@automerge/automerge" = "./node_modules/@automerge/automerge/dist/mjs/entrypoints/fullfat_base64.js";
};
bundleAliasArgs = lib.concatStringsSep " " (
lib.mapAttrsToList
(from: to: "--alias:${from}=${lib.escapeShellArg to}")
bundleAliases
);
nodeRequireBanner = "import { createRequire } from 'module';const require = createRequire(import.meta.url);";
bundleCommand =
if bundle == null
then ""
else if bundleConfig.browserSources or false then ''
${nodejs}/bin/node ${./browser-source.mjs} ${lib.escapeShellArg (builtins.toJSON {
entryPoints = [ bundleConfig.entry ]; bundle = true; platform = bundlePlatform;
target = bundleTarget; format = bundleFormat; alias = bundleAliases;
preserveSymlinks = bundleConfig.preserveSymlinks or true;
banner.js = bundleBanner; outfile = bundleOutfile;
})}
'' else ''
else ''
esbuild ${lib.escapeShellArg bundleConfig.entry} \
--bundle \
--platform=${bundlePlatform} \
--target=${bundleTarget} \
--format=${bundleFormat} \
${bundleAliasArgs} \
${lib.optionalString (bundleConfig.preserveSymlinks or true) "--preserve-symlinks \\"}
--banner:js=${lib.escapeShellArg bundleBanner} \
--outfile=${lib.escapeShellArg bundleOutfile}
@@ -654,22 +595,12 @@ EOF
installConfig.libexecName or (lib.strings.sanitizeDerivationName packageNameFinal);
serverFile = installConfig.serverFile or bundleOutfile;
descriptorPath = installConfig.descriptorPath or "descriptor.quixos-package.txtpb";
extraFiles = installConfig.extraFiles or [ ];
installExtraFile = file:
let
source = toString file.source;
target = file.target or (baseNameOf source);
mode = file.mode or "0644";
in ''
install -Dm${toString mode} ${lib.escapeShellArg source} "$out/libexec/${serverLibexecName}/${target}"
'';
installServerPhase =
if installServer == null
then null
else ''
runHook preInstall
install -Dm755 ${lib.escapeShellArg serverFile} "$out/libexec/${serverLibexecName}/${serverFile}"
${lib.concatMapStringsSep "\n" installExtraFile extraFiles}
mkdir -p "$out/bin"
cat > "$out/bin/${serverBin}" <<EOF
#!${pkgs.runtimeShell}
@@ -679,17 +610,6 @@ EOF
${lib.optionalString (installConfig ? descriptorPath && descriptorPath != null) ''
cp ${lib.escapeShellArg descriptorPath} "$out/${descriptorPath}"
''}
${lib.optionalString (bindingConfig != null) ''
install -m 0444 quixos-check.json "$out/quixos-check.json"
''}
${lib.optionalString (migrationEntrypoint != null) ''
install -Dm444 migration.mjs "$out/libexec/${serverLibexecName}/migration.mjs"
cat > "$out/bin/migrate" <<EOF
#!${pkgs.runtimeShell}
exec ${nodejs}/bin/node --max-old-space-size=256 "$out/libexec/${serverLibexecName}/migration.mjs" "\$@"
EOF
chmod +x "$out/bin/migrate"
''}
runHook postInstall
'';
@@ -698,27 +618,16 @@ EOF
overrideAttrs = old: {
nativeBuildInputs =
(old.nativeBuildInputs or [ ])
++ lib.optional (bundle != null || migrationEntrypoint != null) pkgs.esbuild
++ lib.optional (bundle != null) pkgs.esbuild
++ callOption nativeBuildInputs;
preConfigure = (old.preConfigure or "") + ''
${portalLinksFor (callOption sourcePortals)}
'';
buildPhase = ''
runHook preBuild
${exportsFor (callOption buildEnv)}
${bindingCommand}
${lib.optionalString (bindingConfig != null && bundle != null) "${bindingConfig.generator}/bin/quixos-qx bundle-policy src"}
${lib.optionalString (bindingConfig != null) "yarn exec tsc --noEmit"}
${lib.optionalString (bindingConfig != null) "cp ${lib.escapeShellArg bindingOutput} .qx-checked-bindings"}
${buildCommand}
${lib.optionalString (bindingConfig != null) ''
yarn exec tsc --noEmit
node ${./check-receipt.mjs} ${lib.escapeShellArg (toString bindingSchema)} \
${lib.escapeShellArg bindingConfig.packageRevisionId} ${lib.escapeShellArg bindingOutput} \
${lib.escapeShellArg (toString bindingConfig.generator)} quixos-check.json
''}
${bundleCommand}
${lib.optionalString (migrationEntrypoint != null) ''
esbuild ${lib.escapeShellArg migrationEntrypoint} --bundle --platform=node --target=node24 --format=esm \
${bundleAliasArgs} --preserve-symlinks --banner:js=${lib.escapeShellArg nodeRequireBanner} --outfile=migration.mjs
''}
runHook postBuild
'';
} // lib.optionalAttrs (installServerPhase != null) {
@@ -753,19 +662,11 @@ EOF
devShells.default = pkgs.mkShell {
packages = [
nodejs
project.yarn-freestanding
] ++ lib.optional (bindingConfig != null) generateBindings ++ callOption devShellPackages;
# Explicit command keeps shell entry free of source mutations.
pkgs.yarn-berry_4
] ++ callOption devShellPackages;
shellHook = devShellHookBase + callOption devShellHook;
};
} // maybeServerOutputs;
in (outputsFor null) // {
# The workspace supplies a compiler-produced schema for the exact
# candidate graph. Standalone builds may use checked-in authoring types,
# but only this build path regenerates and witnesses candidate contracts.
quixosPackages.checkedServer = { schema, generator, packageRevisionId }:
(outputsFor { inherit schema generator packageRevisionId; }).packages.server;
}
} // maybeServerOutputs
);
in
{
@@ -774,6 +675,5 @@ in
mkTsPackageServer
mkSchemaSupport
mkCaminoSourcePackage
mkQxBindingSchema
mkCaminoTsYarnNixifyFlake;
}