32652d5279
Enable evolution by default for source-backed workspaces. Add stable conformance ownership, semantic-major review, candidate typechecking, and durable fenced cutover with explicit migrations and forward recovery. Independently supervise package runtimes so unchanged resource owners keep their processes and connections across cutover. Add scoped invocation authority, resource sessions, and typed callback rebinding. Wire opaque object references through generated bindings and RPCs. Add canonical relationship sets, keyed maps, and ordered lists with scoped transactional mutations, revision checks, and inverse consistency. Support planned cascade deletion, protection, tombstones, and lifecycle foundations. Add journaled structural edits, package/function/migration scaffolding, managed repository creation, and resumable bottom-up dependency pin publication. Document lifetime boundaries, revision pinning, prototype compatibility policy, commands, and deferred work. Validate with 210 tests, user-systemd process/connection continuity, generated-package TypeScript checks, and Nix host/protocol checks. TTL handoff, physical reclamation, general multi-step migrations, and root-systemd migration isolation acceptance remain deferred.
757 lines
26 KiB
Nix
757 lines
26 KiB
Nix
let
|
|
mkSchemaSupport =
|
|
{
|
|
pkgs,
|
|
inputs,
|
|
system,
|
|
self ? null,
|
|
flakeRoot,
|
|
schemaDir,
|
|
schemaPrefix ? "packageSchema_",
|
|
schemaBuildCommand ? "yarn build",
|
|
schemaTypecheckCommand ? "yarn typecheck",
|
|
nodejs ? pkgs.nodejs_24,
|
|
git ? pkgs.git,
|
|
}:
|
|
let
|
|
schemaPackageJson = builtins.fromJSON (builtins.readFile "${schemaDir}/package.json");
|
|
schemaMainPath = schemaPackageJson.main or "dist/quixos-package-schema.js";
|
|
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;
|
|
|
|
flakeText =
|
|
builtins.replaceStrings
|
|
[ "\n" "\r" "\t" ]
|
|
[ " " " " " " ]
|
|
(builtins.readFile "${flakeRoot}/flake.nix");
|
|
|
|
getInputUrl = inputName:
|
|
let
|
|
pattern1 = ".*" + inputName + "[[: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;
|
|
|
|
schemaBase =
|
|
(pkgs.callPackage "${schemaDir}/yarn-project.nix" {
|
|
inherit nodejs git;
|
|
})
|
|
{
|
|
# avoid pulling .git, etc.
|
|
src = pkgs.lib.cleanSource schemaDir;
|
|
};
|
|
|
|
schema = schemaBase.overrideAttrs (old: {
|
|
buildPhase = (old.buildPhase or "") + ''
|
|
runHook preBuild
|
|
${schemaBuildCommand}
|
|
runHook postBuild
|
|
'';
|
|
|
|
});
|
|
|
|
schemaCheck = schema.overrideAttrs (old: {
|
|
doCheck = true;
|
|
|
|
checkPhase = (old.checkPhase or "") + ''
|
|
runHook preCheck
|
|
${schemaTypecheckCommand}
|
|
runHook postCheck
|
|
'';
|
|
});
|
|
|
|
schemaInputs =
|
|
pkgs.lib.filterAttrs (name: _: pkgs.lib.hasPrefix schemaPrefix name) inputs;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
schemaMetaJsonByName =
|
|
pkgs.lib.mapAttrs (_: meta: builtins.toJSON meta) schemaMetaByName;
|
|
|
|
schemaExtensionsBlock = pkgs.lib.concatStringsSep "\n" (
|
|
[
|
|
"packageExtensions:"
|
|
" \"quixos-package@*\":"
|
|
" dependencies:"
|
|
]
|
|
++ pkgs.lib.mapAttrsToList (
|
|
name: _: " \"@quixos-package-schemas/${name}\": \"portal:./quixos-package-schemas/${name}\""
|
|
) schemaPackages
|
|
);
|
|
|
|
schemaExtensionsBlockEscaped = pkgs.lib.escapeShellArg schemaExtensionsBlock;
|
|
|
|
schemaLinkCommands = pkgs.lib.concatStringsSep "\n" (
|
|
pkgs.lib.mapAttrsToList (name: drv: ''
|
|
schema_pkg="$(ls -d ${drv}/libexec/* 2>/dev/null | head -n1 || true)"
|
|
if [ -n "$schema_pkg" ]; then
|
|
ln -sfn "$schema_pkg" quixos-package-schemas/${name}
|
|
fi
|
|
'') schemaPackages
|
|
);
|
|
|
|
schemaInstallCommands = pkgs.lib.concatStringsSep "\n" (
|
|
pkgs.lib.mapAttrsToList (name: drv:
|
|
let
|
|
metaJson =
|
|
if pkgs.lib.hasAttr name schemaMetaJsonByName
|
|
then schemaMetaJsonByName.${name}
|
|
else null;
|
|
in
|
|
''
|
|
schema_pkg="$(ls -d ${drv}/libexec/* | head -n1)"
|
|
dest="quixos-package-schemas/${name}"
|
|
rm -rf "$dest"
|
|
cp -R --no-preserve=mode,ownership "$schema_pkg" "$dest"
|
|
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 ""
|
|
)
|
|
) schemaPackages
|
|
);
|
|
|
|
updateYarnrcScript = ''
|
|
quixos_package_schemas_update_yarnrc() {
|
|
local yarnrc=".yarnrc.yml"
|
|
local tmp="$(mktemp)"
|
|
local block_tmp="$(mktemp)"
|
|
printf '%s\n' ${schemaExtensionsBlockEscaped} > "$block_tmp"
|
|
awk -v blockFile="$block_tmp" '
|
|
BEGIN {
|
|
while ((getline line < blockFile) > 0) {
|
|
block = block line "\n"
|
|
}
|
|
}
|
|
/^# BEGIN packageExtensions/ {print; printf "%s", block; skip=1; next}
|
|
/^# END packageExtensions/ {skip=0; print; next}
|
|
!skip {print}
|
|
' "$yarnrc" > "$tmp"
|
|
mv "$tmp" "$yarnrc"
|
|
rm -f "$block_tmp"
|
|
}
|
|
'';
|
|
in
|
|
{
|
|
inherit
|
|
schema
|
|
schemaCheck
|
|
schemaPackages
|
|
schemaLinkCommands
|
|
schemaInstallCommands
|
|
updateYarnrcScript
|
|
schemaExtensionsBlockEscaped
|
|
selfSchemaName;
|
|
};
|
|
|
|
mkTsPackageServer =
|
|
{
|
|
srcDir,
|
|
serverBin ? "server",
|
|
nodejs ? null,
|
|
git ? null,
|
|
templatePreparePackages ? null,
|
|
buildCommand ? null,
|
|
typecheckCommand ? "yarn typecheck",
|
|
serverOverrides ? (old: { }),
|
|
}:
|
|
{
|
|
pkgs,
|
|
schemaSupport,
|
|
extraBuildInputs ? [ ],
|
|
extraNativeBuildInputs ? [ ],
|
|
extraRuntimePackages ? [ ],
|
|
}:
|
|
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
|
|
];
|
|
|
|
packageJson = builtins.fromJSON (builtins.readFile "${srcDir}/package.json");
|
|
packageName = packageJson.name or "quixos-package";
|
|
|
|
base =
|
|
(pkgs.callPackage "${srcDir}/yarn-project.nix" {
|
|
nodejs = nodejs';
|
|
git = git';
|
|
})
|
|
{
|
|
# avoid pulling .git, etc.
|
|
src = pkgs.lib.cleanSource srcDir;
|
|
};
|
|
|
|
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}
|
|
'';
|
|
|
|
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;
|
|
|
|
# node-gyp will look at these
|
|
PYTHON = "${pkgs.python3}/bin/python3";
|
|
npm_config_python = "${pkgs.python3}/bin/python3";
|
|
|
|
preConfigure = (old.preConfigure or "") + ''
|
|
mkdir -p quixos-package-schemas
|
|
${schemaSupport.schemaLinkCommands}
|
|
'';
|
|
|
|
buildPhase =
|
|
(old.buildPhase or "")
|
|
+ (
|
|
if buildCommand != null
|
|
then ''
|
|
runHook preBuildQuixos
|
|
${buildCommand}
|
|
runHook postBuildQuixos
|
|
''
|
|
else ""
|
|
);
|
|
|
|
postFixup = (old.postFixup or "") + runtimeWrap;
|
|
|
|
postInstall = (old.postInstall or "") + ''
|
|
pkg_root="$out/libexec/${old.name}"
|
|
mkdir -p "$pkg_root/quixos-package-schemas"
|
|
(cd "$pkg_root" && ${schemaSupport.schemaInstallCommands})
|
|
'';
|
|
} // extraAttrs
|
|
);
|
|
|
|
check = server.overrideAttrs (old: {
|
|
doCheck = true;
|
|
|
|
checkPhase = (old.checkPhase or "") + ''
|
|
runHook preCheck
|
|
${typecheckCommand}
|
|
runHook postCheck
|
|
'';
|
|
});
|
|
|
|
prepareShell = pkgs.mkShell {
|
|
packages = templatePreparePackages';
|
|
};
|
|
in
|
|
{
|
|
inherit server check packageName serverBin;
|
|
|
|
devShells = {
|
|
"quixos-prepare" = prepareShell;
|
|
};
|
|
};
|
|
|
|
mkQuixosPackageFlake =
|
|
{
|
|
self ? null,
|
|
inputs,
|
|
nixpkgs,
|
|
flake-utils,
|
|
flakeRoot,
|
|
schemaDir,
|
|
schemaPrefix ? "packageSchema_",
|
|
serverBuilder,
|
|
packageName ? null,
|
|
extraBuildInputs ? (pkgs: [ ]),
|
|
extraNativeBuildInputs ? (pkgs: [ ]),
|
|
extraRuntimePackages ? (pkgs: [ ]),
|
|
extraDevShellPackages ? (pkgs: [ ]),
|
|
enableYarnrcHook ? true,
|
|
extraDevShellHook ? "",
|
|
appProgram ? null,
|
|
}:
|
|
flake-utils.lib.eachDefaultSystem (
|
|
system:
|
|
let
|
|
pkgs = import nixpkgs { inherit system; };
|
|
|
|
schemaSupport = mkSchemaSupport {
|
|
inherit pkgs inputs system schemaDir schemaPrefix self flakeRoot;
|
|
};
|
|
|
|
normalizeList = value:
|
|
if builtins.isFunction value
|
|
then value pkgs
|
|
else value;
|
|
|
|
serverResult = serverBuilder {
|
|
inherit pkgs schemaSupport;
|
|
extraBuildInputs = normalizeList extraBuildInputs;
|
|
extraNativeBuildInputs = normalizeList extraNativeBuildInputs;
|
|
extraRuntimePackages = normalizeList extraRuntimePackages;
|
|
};
|
|
|
|
combinedName = pkgs.lib.strings.sanitizeDerivationName (
|
|
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"}";
|
|
|
|
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}"
|
|
fi
|
|
fi
|
|
''
|
|
else "";
|
|
|
|
devShellHook = devShellHookBase + extraDevShellHook;
|
|
|
|
devShellPackages = normalizeList extraDevShellPackages;
|
|
|
|
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 ];
|
|
};
|
|
packages.server = packageServer;
|
|
packages.schema = schemaSupport.schema;
|
|
|
|
apps.default = {
|
|
type = "app";
|
|
program = appProgramFinal;
|
|
meta.description = "Quixos package server";
|
|
};
|
|
|
|
checks = checks;
|
|
|
|
devShells = extraDevShells // {
|
|
default = pkgs.mkShell {
|
|
inputsFrom = [ packageServer ] ++ builtins.attrValues schemaSupport.schemaPackages;
|
|
packages = devShellPackages;
|
|
shellHook = devShellHook;
|
|
};
|
|
};
|
|
}
|
|
);
|
|
|
|
mkCaminoSourcePackage =
|
|
{
|
|
pkgs,
|
|
src,
|
|
name ? "quixos-package-source",
|
|
}:
|
|
pkgs.stdenvNoCC.mkDerivation {
|
|
inherit name;
|
|
src = pkgs.lib.cleanSource src;
|
|
dontConfigure = true;
|
|
dontBuild = true;
|
|
installPhase = ''
|
|
runHook preInstall
|
|
cp -R . "$out"
|
|
runHook postInstall
|
|
'';
|
|
};
|
|
|
|
# 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,
|
|
nixpkgs,
|
|
flake-utils,
|
|
packageRoot,
|
|
packageName ? null,
|
|
sourceName ? "quixos-package-source",
|
|
promptName ? null,
|
|
nodejsAttr ? "nodejs_24",
|
|
buildCommand ? "yarn build",
|
|
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 ? "",
|
|
bundle ? null,
|
|
installServer ? null,
|
|
}:
|
|
flake-utils.lib.eachDefaultSystem (
|
|
system:
|
|
let
|
|
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;
|
|
|
|
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;
|
|
};
|
|
|
|
exportsFor = attrs:
|
|
lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList
|
|
(name: value: "export ${name}=${lib.escapeShellArg (toString value)}")
|
|
attrs
|
|
);
|
|
|
|
bindingConfig = 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";
|
|
bindingOptions = if bindingConfig == null then null else
|
|
pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (bindingConfig.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} ${bindingOptions}
|
|
'';
|
|
generateBindings = pkgs.writeShellApplication {
|
|
name = "qx-generate-bindings";
|
|
text = bindingCommand;
|
|
};
|
|
|
|
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 ''
|
|
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}
|
|
'';
|
|
|
|
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
|
|
'';
|
|
|
|
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) "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
|
|
'';
|
|
|
|
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;
|
|
};
|
|
} // maybeServerOutputs
|
|
);
|
|
in
|
|
{
|
|
inherit
|
|
mkQuixosPackageFlake
|
|
mkTsPackageServer
|
|
mkSchemaSupport
|
|
mkCaminoSourcePackage
|
|
mkQxBindingSchema
|
|
mkCaminoTsYarnNixifyFlake;
|
|
}
|