858 lines
30 KiB
Nix
858 lines
30 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.gen.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.gen.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
|
|
outputsFor =
|
|
candidateBindings:
|
|
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 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.gen.ts" else bindingConfig.output or "src/gen/qx.gen.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;
|
|
};
|
|
|
|
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}
|
|
'';
|
|
|
|
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.gen.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.gen.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
|
|
'';
|
|
|
|
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
|
|
(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;
|
|
}
|
|
);
|
|
in
|
|
{
|
|
inherit
|
|
mkQuixosPackageFlake
|
|
mkTsPackageServer
|
|
mkSchemaSupport
|
|
mkCaminoSourcePackage
|
|
mkQxBindingSchema
|
|
mkCaminoTsYarnNixifyFlake
|
|
;
|
|
}
|