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 318 additions and 613 deletions
-8
View File
@@ -1,8 +0,0 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
"sourceCommit": "1de28b236de076d239752b77553f833dfbdb5b43",
"sourcePath": "quixos-instance/quixos-nix-helpers",
"exportName": "quixos-nix-helpers",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
}
-69
View File
@@ -1,69 +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),
};
});
},
},
],
});
-40
View File
@@ -1,40 +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" });
+318 -496
View File
@@ -19,25 +19,23 @@ let
schemaMainPathDefault = "dist/quixos-package-schema.js";
schemaPackageName = schemaPackageJson.name or "schema";
selfSchemaName =
if pkgs.lib.hasInfix "/" schemaPackageName then
pkgs.lib.last (pkgs.lib.splitString "/" schemaPackageName)
else
schemaPackageName;
if pkgs.lib.hasInfix "/" schemaPackageName
then pkgs.lib.last (pkgs.lib.splitString "/" schemaPackageName)
else schemaPackageName;
flakeText = builtins.replaceStrings [ "\n" "\r" "\t" ] [ " " " " " " ] (
builtins.readFile "${flakeRoot}/flake.nix"
);
flakeText =
builtins.replaceStrings
[ "\n" "\r" "\t" ]
[ " " " " " " ]
(builtins.readFile "${flakeRoot}/flake.nix");
getInputUrl =
inputName:
getInputUrl = inputName:
let
pattern1 = ".*" + inputName + "[[:space:]]*\\.url[[:space:]]*=[[:space:]]*\"([^\"]+)\".*";
pattern2 =
".*" + inputName + "[[:space:]]*=[[:space:]]*\\{[^}]*url[[:space:]]*=[[:space:]]*\"([^\"]+)\".*";
pattern2 = ".*" + inputName + "[[:space:]]*=[[:space:]]*\\{[^}]*url[[:space:]]*=[[:space:]]*\"([^\"]+)\".*";
match1 = builtins.match pattern1 flakeText;
match2 = if match1 != null then match1 else builtins.match pattern2 flakeText;
in
if match2 == null then null else builtins.elemAt match2 0;
in if match2 == null then null else builtins.elemAt match2 0;
schemaBase =
(pkgs.callPackage "${schemaDir}/yarn-project.nix" {
@@ -67,61 +65,68 @@ let
'';
});
schemaInputs = pkgs.lib.filterAttrs (name: _: pkgs.lib.hasPrefix schemaPrefix name) inputs;
schemaInputs =
pkgs.lib.filterAttrs (name: _: pkgs.lib.hasPrefix schemaPrefix name) inputs;
decodeSchemaInputName = encodedName: builtins.replaceStrings [ "__" ] [ "." ] encodedName;
decodeSchemaInputName = encodedName:
builtins.replaceStrings [ "__" ] [ "." ] encodedName;
schemaPackagesFromInputs = pkgs.lib.mapAttrs' (
name: flake:
let
schemaName = decodeSchemaInputName (pkgs.lib.removePrefix schemaPrefix name);
in
{
name = schemaName;
value = flake.packages.${system}.schema;
}
) schemaInputs;
schemaPackagesFromInputs =
pkgs.lib.mapAttrs'
(name: flake:
let
schemaName =
decodeSchemaInputName (pkgs.lib.removePrefix schemaPrefix name);
in
{
name = schemaName;
value = flake.packages.${system}.schema;
})
schemaInputs;
schemaPackages = schemaPackagesFromInputs // {
"${selfSchemaName}" = schema;
};
schemaPackages = schemaPackagesFromInputs // { "${selfSchemaName}" = schema; };
schemaMetaByName = pkgs.lib.mapAttrs' (
name: flake:
let
schemaName = decodeSchemaInputName (pkgs.lib.removePrefix schemaPrefix name);
sourceInfo = if flake ? sourceInfo then flake.sourceInfo else { };
sourceRev = sourceInfo.rev or null;
inputUrl = getInputUrl name;
urlBase = if inputUrl == null then null else builtins.head (pkgs.lib.splitString "?" inputUrl);
isDisallowed =
inputUrl == null
|| pkgs.lib.hasPrefix "path:" inputUrl
|| pkgs.lib.hasPrefix "file:" inputUrl
|| pkgs.lib.hasPrefix "git+file:" inputUrl;
flakeRef =
if isDisallowed || sourceRev == null || urlBase == null then
null
else
urlBase + "?rev=" + sourceRev;
meta = {
name = "@quixos-package-schemas/${schemaName}";
type = sourceInfo.type or null;
url = inputUrl;
rev = sourceRev;
flakeRef = flakeRef;
};
in
if flakeRef == null then
throw "Schema input ${schemaName} must be a git flake with url+rev"
else
{
name = schemaName;
value = meta;
}
) schemaInputs;
schemaMetaByName =
pkgs.lib.mapAttrs'
(name: flake:
let
schemaName =
decodeSchemaInputName (pkgs.lib.removePrefix schemaPrefix name);
sourceInfo = if flake ? sourceInfo then flake.sourceInfo else { };
sourceRev = sourceInfo.rev or null;
inputUrl = getInputUrl name;
urlBase =
if inputUrl == null
then null
else builtins.head (pkgs.lib.splitString "?" inputUrl);
isDisallowed =
inputUrl == null
|| pkgs.lib.hasPrefix "path:" inputUrl
|| pkgs.lib.hasPrefix "file:" inputUrl
|| pkgs.lib.hasPrefix "git+file:" inputUrl;
flakeRef =
if isDisallowed || sourceRev == null || urlBase == null
then null
else urlBase + "?rev=" + sourceRev;
meta =
{
name = "@quixos-package-schemas/${schemaName}";
type = sourceInfo.type or null;
url = inputUrl;
rev = sourceRev;
flakeRef = flakeRef;
};
in
if flakeRef == null
then throw "Schema input ${schemaName} must be a git flake with url+rev"
else {
name = schemaName;
value = meta;
})
schemaInputs;
schemaMetaJsonByName = pkgs.lib.mapAttrs (_: meta: builtins.toJSON meta) schemaMetaByName;
schemaMetaJsonByName =
pkgs.lib.mapAttrs (_: meta: builtins.toJSON meta) schemaMetaByName;
schemaExtensionsBlock = pkgs.lib.concatStringsSep "\n" (
[
@@ -146,11 +151,12 @@ let
);
schemaInstallCommands = pkgs.lib.concatStringsSep "\n" (
pkgs.lib.mapAttrsToList (
name: drv:
pkgs.lib.mapAttrsToList (name: drv:
let
metaJson =
if pkgs.lib.hasAttr name schemaMetaJsonByName then schemaMetaJsonByName.${name} else null;
if pkgs.lib.hasAttr name schemaMetaJsonByName
then schemaMetaJsonByName.${name}
else null;
in
''
schema_pkg="$(ls -d ${drv}/libexec/* | head -n1)"
@@ -160,47 +166,46 @@ let
chmod -R u+w "$dest"
''
+ (
if metaJson != null then
''
schema_main="${schemaMainPathDefault}"
if [ -f "$dest/$schema_main" ]; then
block_tmp="$(mktemp -p "$dest")"
tmp_out="$(mktemp -p "$dest")"
cat > "$block_tmp" <<'EOF'
packageSchema.__quixos = ${metaJson};
EOF
chmod u+w "$dest/$schema_main"
if grep -q '^export default ' "$dest/$schema_main"; then
awk -v blockFile="$block_tmp" '
BEGIN {
while ((getline line < blockFile) > 0) {
block = block line "\n"
}
}
!inserted && /^export default / { printf "%s", block; inserted=1 }
{ print }
' "$dest/$schema_main" > "$tmp_out"
cat "$tmp_out" > "$dest/$schema_main"
elif grep -q '^//# sourceMappingURL=' "$dest/$schema_main"; then
awk -v blockFile="$block_tmp" '
BEGIN {
while ((getline line < blockFile) > 0) {
block = block line "\n"
}
}
/^\/\/# sourceMappingURL=/ { printf "%s", block; print; next }
{ print }
' "$dest/$schema_main" > "$tmp_out"
cat "$tmp_out" > "$dest/$schema_main"
else
printf '\n' >> "$dest/$schema_main"
cat "$block_tmp" >> "$dest/$schema_main"
fi
rm -f "$block_tmp" "$tmp_out"
fi
''
else
""
if metaJson != null
then ''
schema_main="${schemaMainPathDefault}"
if [ -f "$dest/$schema_main" ]; then
block_tmp="$(mktemp -p "$dest")"
tmp_out="$(mktemp -p "$dest")"
cat > "$block_tmp" <<'EOF'
packageSchema.__quixos = ${metaJson};
EOF
chmod u+w "$dest/$schema_main"
if grep -q '^export default ' "$dest/$schema_main"; then
awk -v blockFile="$block_tmp" '
BEGIN {
while ((getline line < blockFile) > 0) {
block = block line "\n"
}
}
!inserted && /^export default / { printf "%s", block; inserted=1 }
{ print }
' "$dest/$schema_main" > "$tmp_out"
cat "$tmp_out" > "$dest/$schema_main"
elif grep -q '^//# sourceMappingURL=' "$dest/$schema_main"; then
awk -v blockFile="$block_tmp" '
BEGIN {
while ((getline line < blockFile) > 0) {
block = block line "\n"
}
}
/^\/\/# sourceMappingURL=/ { printf "%s", block; print; next }
{ print }
' "$dest/$schema_main" > "$tmp_out"
cat "$tmp_out" > "$dest/$schema_main"
else
printf '\n' >> "$dest/$schema_main"
cat "$block_tmp" >> "$dest/$schema_main"
fi
rm -f "$block_tmp" "$tmp_out"
fi
''
else ""
)
) schemaPackages
);
@@ -235,8 +240,7 @@ let
schemaInstallCommands
updateYarnrcScript
schemaExtensionsBlockEscaped
selfSchemaName
;
selfSchemaName;
};
mkTsPackageServer =
@@ -261,16 +265,15 @@ let
nodejs' = if nodejs != null then nodejs else pkgs.nodejs_24;
git' = if git != null then git else pkgs.git;
templatePreparePackages' =
if templatePreparePackages != null then
templatePreparePackages
else
[
pkgs.findutils
git'
pkgs.gnused
nodejs'
pkgs.yarn-berry_4
];
if templatePreparePackages != null
then templatePreparePackages
else [
pkgs.findutils
git'
pkgs.gnused
nodejs'
pkgs.yarn-berry_4
];
packageJson = builtins.fromJSON (builtins.readFile "${srcDir}/package.json");
packageName = packageJson.name or "quixos-package";
@@ -288,33 +291,28 @@ let
runtimeBinPath = pkgs.lib.makeBinPath extraRuntimePackages;
runtimeLibPath = pkgs.lib.makeLibraryPath extraRuntimePackages;
runtimeWrap =
if extraRuntimePackages == [ ] then
""
else
''
wrapProgram "$out/bin/${serverBin}" \
--prefix PATH : ${runtimeBinPath} \
--prefix LD_LIBRARY_PATH : ${runtimeLibPath}
'';
if extraRuntimePackages == [ ]
then ""
else ''
wrapProgram "$out/bin/${serverBin}" \
--prefix PATH : ${runtimeBinPath} \
--prefix LD_LIBRARY_PATH : ${runtimeLibPath}
'';
server = base.overrideAttrs (
old:
server = base.overrideAttrs (old:
let
extraAttrs = serverOverrides old;
in
{
buildInputs = (old.buildInputs or [ ]) ++ extraBuildInputs;
nativeBuildInputs =
(old.nativeBuildInputs or [ ])
++ [
pkgs.python3
pkgs.gnumake
pkgs.gcc
pkgs.pkg-config
pkgs.makeWrapper
]
++ extraNativeBuildInputs;
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
pkgs.python3
pkgs.gnumake
pkgs.gcc
pkgs.pkg-config
pkgs.makeWrapper
] ++ extraNativeBuildInputs;
# node-gyp will look at these
PYTHON = "${pkgs.python3}/bin/python3";
@@ -328,14 +326,13 @@ let
buildPhase =
(old.buildPhase or "")
+ (
if buildCommand != null then
''
runHook preBuildQuixos
${buildCommand}
runHook postBuildQuixos
''
else
""
if buildCommand != null
then ''
runHook preBuildQuixos
${buildCommand}
runHook postBuildQuixos
''
else ""
);
postFixup = (old.postFixup or "") + runtimeWrap;
@@ -345,8 +342,7 @@ let
mkdir -p "$pkg_root/quixos-package-schemas"
(cd "$pkg_root" && ${schemaSupport.schemaInstallCommands})
'';
}
// extraAttrs
} // extraAttrs
);
check = server.overrideAttrs (old: {
@@ -364,12 +360,7 @@ let
};
in
{
inherit
server
check
packageName
serverBin
;
inherit server check packageName serverBin;
devShells = {
"quixos-prepare" = prepareShell;
@@ -401,18 +392,13 @@ let
pkgs = import nixpkgs { inherit system; };
schemaSupport = mkSchemaSupport {
inherit
pkgs
inputs
system
schemaDir
schemaPrefix
self
flakeRoot
;
inherit pkgs inputs system schemaDir schemaPrefix self flakeRoot;
};
normalizeList = value: if builtins.isFunction value then value pkgs else value;
normalizeList = value:
if builtins.isFunction value
then value pkgs
else value;
serverResult = serverBuilder {
inherit pkgs schemaSupport;
@@ -422,75 +408,68 @@ let
};
combinedName = pkgs.lib.strings.sanitizeDerivationName (
if packageName != null then
packageName
else if serverResult ? packageName then
serverResult.packageName
else
"quixos-package"
if packageName != null
then packageName
else if serverResult ? packageName
then serverResult.packageName
else "quixos-package"
);
packageServer = serverResult.server;
packageServerCheck = if serverResult ? check then serverResult.check else null;
appProgramFinal =
if appProgram != null then
appProgram
else if serverResult ? appProgram then
serverResult.appProgram
else
"${packageServer}/bin/${serverResult.serverBin or "server"}";
if appProgram != null
then appProgram
else if serverResult ? appProgram
then serverResult.appProgram
else "${packageServer}/bin/${serverResult.serverBin or "server"}";
devShellHookBase =
if enableYarnrcHook then
''
find_schema_root() {
dir="$PWD"
while [ "$dir" != "/" ]; do
if [ -f "$dir/src/.yarnrc.yml" ] && [ -d "$dir/schema" ]; then
printf "%s\n" "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}
${schemaSupport.updateYarnrcScript}
link_schemas() {
mkdir -p quixos-package-schemas
${schemaSupport.schemaLinkCommands}
}
base="$(find_schema_root || true)"
if [ -n "$base" ]; then
(cd "$base/src" && link_schemas && quixos_package_schemas_update_yarnrc)
if [ -d "$base/schema" ]; then
ln -sfn "$base/schema" "$base/src/quixos-package-schemas/${schemaSupport.selfSchemaName}"
if enableYarnrcHook
then ''
find_schema_root() {
dir="$PWD"
while [ "$dir" != "/" ]; do
if [ -f "$dir/src/.yarnrc.yml" ] && [ -d "$dir/schema" ]; then
printf "%s\n" "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}
${schemaSupport.updateYarnrcScript}
link_schemas() {
mkdir -p quixos-package-schemas
${schemaSupport.schemaLinkCommands}
}
base="$(find_schema_root || true)"
if [ -n "$base" ]; then
(cd "$base/src" && link_schemas && quixos_package_schemas_update_yarnrc)
if [ -d "$base/schema" ]; then
ln -sfn "$base/schema" "$base/src/quixos-package-schemas/${schemaSupport.selfSchemaName}"
fi
''
else
"";
fi
''
else "";
devShellHook = devShellHookBase + extraDevShellHook;
devShellPackages = normalizeList extraDevShellPackages;
checks = {
schema = schemaSupport.schemaCheck;
}
// (if packageServerCheck != null then { default = packageServerCheck; } else { });
checks =
{ schema = schemaSupport.schemaCheck; }
// (if packageServerCheck != null then { default = packageServerCheck; } else { });
extraDevShells = if serverResult ? devShells then serverResult.devShells else { };
in
{
packages.default = pkgs.symlinkJoin {
name = combinedName;
paths = [
packageServer
schemaSupport.schema
];
paths = [ packageServer schemaSupport.schema ];
};
packages.server = packageServer;
packages.schema = schemaSupport.schema;
@@ -531,29 +510,6 @@ let
'';
};
# 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,
@@ -565,13 +521,8 @@ let
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 ? "",
@@ -581,268 +532,141 @@ let
flake-utils.lib.eachDefaultSystem (
system:
let
outputsFor =
candidateBindings:
let
pkgs = import nixpkgs { inherit system; };
lib = pkgs.lib;
nodejs = pkgs.${nodejsAttr};
pkgs = import nixpkgs { inherit system; };
lib = pkgs.lib;
nodejs = pkgs.${nodejsAttr};
callOption =
value: if builtins.isFunction value then value { inherit inputs pkgs system; } else value;
callOption = value:
if builtins.isFunction value
then value { inherit inputs pkgs system; }
else value;
packageJson = builtins.fromJSON (builtins.readFile "${packageRoot}/package.json");
packageNameFinal =
if packageName != null then packageName else packageJson.name or "quixos-package";
promptNameFinal =
if promptName != null then promptName else lib.strings.sanitizeDerivationName packageNameFinal;
packageJson = builtins.fromJSON (builtins.readFile "${packageRoot}/package.json");
packageNameFinal = if packageName != null then packageName else packageJson.name or "quixos-package";
promptNameFinal =
if promptName != null
then promptName
else lib.strings.sanitizeDerivationName packageNameFinal;
sourcePackage = mkCaminoSourcePackage {
inherit pkgs;
src = packageRoot;
name = sourceName;
};
sourcePackage = mkCaminoSourcePackage {
inherit pkgs;
src = packageRoot;
name = sourceName;
};
exportsFor =
attrs:
lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: value: "export ${name}=${lib.escapeShellArg (toString value)}") attrs
);
exportsFor = attrs:
lib.concatStringsSep "\n" (
lib.mapAttrsToList
(name: value: "export ${name}=${lib.escapeShellArg (toString value)}")
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";
bundlePlatform = bundleConfig.platform or "node";
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
''
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}
'';
bundleConfig = if bundle == null then { } else bundle;
bundleOutfile = bundleConfig.outfile or "server.mjs";
bundlePlatform = bundleConfig.platform or "node";
bundleTarget = bundleConfig.target or "node24";
bundleFormat = bundleConfig.format or "esm";
bundleBanner = bundleConfig.banner or nodeRequireBanner;
nodeRequireBanner = "import { createRequire } from 'module';const require = createRequire(import.meta.url);";
bundleCommand =
if bundle == null
then ""
else ''
esbuild ${lib.escapeShellArg bundleConfig.entry} \
--bundle \
--platform=${bundlePlatform} \
--target=${bundleTarget} \
--format=${bundleFormat} \
${lib.optionalString (bundleConfig.preserveSymlinks or true) "--preserve-symlinks \\"}
--banner:js=${lib.escapeShellArg bundleBanner} \
--outfile=${lib.escapeShellArg bundleOutfile}
'';
installConfig = if installServer == null then { } else installServer;
serverBin = installConfig.binName or "server";
serverLibexecName =
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}
exec ${nodejs}/bin/node "$out/libexec/${serverLibexecName}/${serverFile}" "\$@"
EOF
chmod +x "$out/bin/${serverBin}"
${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
'';
installConfig = if installServer == null then { } else installServer;
serverBin = installConfig.binName or "server";
serverLibexecName =
installConfig.libexecName or (lib.strings.sanitizeDerivationName packageNameFinal);
serverFile = installConfig.serverFile or bundleOutfile;
descriptorPath = installConfig.descriptorPath or "descriptor.quixos-package.txtpb";
installServerPhase =
if installServer == null
then null
else ''
runHook preInstall
install -Dm755 ${lib.escapeShellArg serverFile} "$out/libexec/${serverLibexecName}/${serverFile}"
mkdir -p "$out/bin"
cat > "$out/bin/${serverBin}" <<EOF
#!${pkgs.runtimeShell}
exec ${nodejs}/bin/node "$out/libexec/${serverLibexecName}/${serverFile}" "\$@"
EOF
chmod +x "$out/bin/${serverBin}"
${lib.optionalString (installConfig ? descriptorPath && descriptorPath != null) ''
cp ${lib.escapeShellArg descriptorPath} "$out/${descriptorPath}"
''}
runHook postInstall
'';
project = (pkgs.callPackage "${packageRoot}/yarn-project.nix" { inherit nodejs; }) {
src = packageRoot;
overrideAttrs =
old:
{
nativeBuildInputs =
(old.nativeBuildInputs or [ ])
++ lib.optional (bundle != null || migrationEntrypoint != null) pkgs.esbuild
++ callOption nativeBuildInputs;
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) {
installPhase = installServerPhase;
};
};
devShellHookBase = ''
if [ -n "''${PS1-}" ]; then
if [ -n "''${QX_DEV_SHELL_PROMPT-}" ]; then
PS1="''${PS1#\[qx:''${QX_DEV_SHELL_PROMPT}\] }"
fi
export QX_DEV_SHELL_PROMPT=${lib.escapeShellArg promptNameFinal}
PS1="[qx:${promptNameFinal}] $PS1"
fi
project = (pkgs.callPackage "${packageRoot}/yarn-project.nix" { inherit nodejs; }) {
src = packageRoot;
overrideAttrs = old: {
nativeBuildInputs =
(old.nativeBuildInputs or [ ])
++ lib.optional (bundle != null) pkgs.esbuild
++ callOption nativeBuildInputs;
preConfigure = (old.preConfigure or "") + ''
${portalLinksFor (callOption sourcePortals)}
'';
buildPhase = ''
runHook preBuild
${exportsFor (callOption buildEnv)}
${buildCommand}
${bundleCommand}
runHook postBuild
'';
} // lib.optionalAttrs (installServerPhase != null) {
installPhase = installServerPhase;
};
};
maybeServerOutputs =
if installServer == null then
{ }
else
{
packages.server = project;
apps.default = {
type = "app";
program = "${project}/bin/${serverBin}";
};
};
in
{
packages.default = project;
packages.source = sourcePackage;
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.
shellHook = devShellHookBase + callOption devShellHook;
devShellHookBase = ''
if [ -n "''${PS1-}" ]; then
if [ -n "''${QX_DEV_SHELL_PROMPT-}" ]; then
PS1="''${PS1#\[qx:''${QX_DEV_SHELL_PROMPT}\] }"
fi
export QX_DEV_SHELL_PROMPT=${lib.escapeShellArg promptNameFinal}
PS1="[qx:${promptNameFinal}] $PS1"
fi
'';
maybeServerOutputs =
if installServer == null
then { }
else {
packages.server = project;
apps.default = {
type = "app";
program = "${project}/bin/${serverBin}";
};
}
// 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;
}
{
packages.default = project;
packages.source = sourcePackage;
devShells.default = pkgs.mkShell {
packages = [
nodejs
pkgs.yarn-berry_4
] ++ callOption devShellPackages;
shellHook = devShellHookBase + callOption devShellHook;
};
} // maybeServerOutputs
);
in
{
@@ -851,7 +675,5 @@ in
mkTsPackageServer
mkSchemaSupport
mkCaminoSourcePackage
mkQxBindingSchema
mkCaminoTsYarnNixifyFlake
;
mkCaminoTsYarnNixifyFlake;
}