Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 223 additions & 1 deletion packages/self-host-runtime/src/install-script.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import { spawnSync } from "node:child_process";
import {
chmod,
mkdtemp,
mkdir,
readFile,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { describe, expect, it } from "vitest";

import {
Expand All @@ -6,6 +18,11 @@ import {
shouldServeInstallScript,
} from "./install-script";

async function writeExecutable(path: string, content: string): Promise<void> {
await writeFile(path, content);
await chmod(path, 0o755);
}

describe("install script surface", () => {
it("keeps the SPA root for browser navigation", () => {
const request = new Request("https://onequery.dev/", {
Expand Down Expand Up @@ -75,18 +92,223 @@ describe("install script surface", () => {
const escapedTargetTriple = `\\${dollar}{target_triple}`;

expect(script).toContain("cat <<'EOF'");
expect(script).toContain('TARGET_TRIPLE="$target_triple"');
expect(script).toContain('TARGET_TRIPLE="$launcher_target_triple"');
expect(script).toContain(
'INSTALL_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)'
);
expect(script).toContain(
`export ONEQUERY_RUNTIME_ROOT="${runtimeRootDefault}"`
);
expect(script).toContain(
'managed_node_path="$INSTALL_DIR/runtime/node/bin/node"'
);
expect(script).toContain(
'exec "$INSTALL_DIR/vendor/$TARGET_TRIPLE/onequery/onequery" "$@"'
);
expect(script).not.toContain(
`vendor/${escapedTargetTriple}/onequery/onequery`
);
});

it("runs under /bin/sh when managed Node.js installation is required", async () => {
const tempDir = await mkdtemp(join(tmpdir(), "onequery-install-script-"));
try {
const mockBinDir = join(tempDir, "mock-bin");
const installRoot = join(tempDir, "install-root");
const binDir = join(tempDir, "bin");
const shellTmpDir = join(tempDir, "tmp");
const scriptPath = join(tempDir, "install.sh");

await mkdir(mockBinDir, { recursive: true });
await mkdir(installRoot, { recursive: true });
await mkdir(binDir, { recursive: true });
await mkdir(shellTmpDir, { recursive: true });
await writeFile(scriptPath, createInstallScript());
await chmod(scriptPath, 0o755);

await writeExecutable(
join(mockBinDir, "uname"),
`#!/bin/sh
case "$1" in
-s)
printf '%s\\n' 'Darwin'
;;
-m)
printf '%s\\n' 'arm64'
;;
*)
/usr/bin/uname "$@"
;;
esac
`
);

await writeExecutable(
join(mockBinDir, "node"),
`#!/bin/sh
if [ "$1" = "--version" ]; then
printf '%s\\n' 'v22.22.0'
exit 0
fi

printf 'unexpected node invocation: %s\\n' "$*" >&2
exit 1
`
);

await writeExecutable(
join(mockBinDir, "curl"),
`#!/bin/sh
out_path=''
url=''

while [ "$#" -gt 0 ]; do
case "$1" in
-o)
out_path="$2"
shift 2
;;
-f|-s|-S|-L|-fsSL)
shift
;;
-*)
shift
;;
*)
url="$1"
shift
;;
esac
done

case "$url" in
*/SHASUMS256.txt)
printf '%s\\n' '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef node-v24.11.0-darwin-arm64.tar.gz'
;;
*/onequery-npm.tgz|*/onequery-npm-darwin-arm64.tgz|*/node-v24.11.0-darwin-arm64.tar.gz)
: > "$out_path"
;;
*)
printf 'unexpected curl url: %s\\n' "$url" >&2
exit 1
;;
esac
`
);

await writeExecutable(
join(mockBinDir, "tar"),
`#!/bin/sh
archive_path=''
extract_dir=''

while [ "$#" -gt 0 ]; do
case "$1" in
-xzf)
archive_path="$2"
shift 2
;;
-C)
extract_dir="$2"
shift 2
;;
*)
shift
;;
esac
done

case "$(basename "$archive_path")" in
root.tgz)
mkdir -p "$extract_dir/package"
cat > "$extract_dir/package/package.json" <<'EOF'
{"version":"0.1.22"}
EOF
;;
platform.tgz)
mkdir -p "$extract_dir/package/vendor/aarch64-apple-darwin/onequery"
cat > "$extract_dir/package/vendor/aarch64-apple-darwin/onequery/onequery" <<'EOF'
#!/bin/sh
exit 0
EOF
chmod 755 "$extract_dir/package/vendor/aarch64-apple-darwin/onequery/onequery"
;;
node-v24.11.0-darwin-arm64.tar.gz)
mkdir -p "$extract_dir/node-v24.11.0-darwin-arm64/bin"
cat > "$extract_dir/node-v24.11.0-darwin-arm64/bin/node" <<'EOF'
#!/bin/sh
printf '%s\\n' 'v24.11.0'
EOF
chmod 755 "$extract_dir/node-v24.11.0-darwin-arm64/bin/node"
;;
*)
printf 'unexpected tar archive: %s\\n' "$archive_path" >&2
exit 1
;;
esac
`
);

const shellEnv = {
...process.env,
HOME: tempDir,
ONEQUERY_BIN_DIR: binDir,
ONEQUERY_INSTALL_ROOT: installRoot,
PATH: `${mockBinDir}:${process.env.PATH ?? ""}`,
TMPDIR: shellTmpDir,
};

const result = spawnSync("/bin/sh", [scriptPath], {
cwd: tempDir,
encoding: "utf8",
env: shellEnv,
});

expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("Installed onequery 0.1.22");

const launcherPath = join(
installRoot,
"versions",
"0.1.22",
"bin",
"onequery"
);
const launcher = await readFile(launcherPath, "utf8");

expect(launcher).toContain(
'export ONEQUERY_SERVER_JS_RUNTIME="$managed_node_path"'
);

const launcherSyntaxCheck = spawnSync("/bin/sh", ["-n", launcherPath], {
cwd: tempDir,
encoding: "utf8",
env: shellEnv,
});
expect(launcherSyntaxCheck.status).toBe(0);
expect(launcherSyntaxCheck.stderr).toBe("");

const launcherRun = spawnSync("/bin/sh", [launcherPath], {
cwd: tempDir,
encoding: "utf8",
env: shellEnv,
});
expect(launcherRun.status).toBe(0);
expect(launcherRun.stderr).toBe("");

const managedNodePath = join(
installRoot,
"versions",
"0.1.22",
"runtime",
"node",
"bin",
"node"
);
expect(await readFile(managedNodePath, "utf8")).toContain("v24.11.0");
} finally {
await rm(tempDir, { force: true, recursive: true });
}
});
});
62 changes: 29 additions & 33 deletions packages/self-host-runtime/src/install-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const RELEASE_BASE_URL =

const CURL_LIKE_USER_AGENT_PATTERN = /\b(curl|wget|httpie)\b/i;
const PACKAGED_CLI_DIR = getRuntimeBundleDirectoryConfig("cli").relativePath;
const MANAGED_NODE_BIN_RELATIVE_PATH = "runtime/node/bin/node";
const PACKAGED_SERVER_JS_RUNTIME_ENV_VAR = "ONEQUERY_SERVER_JS_RUNTIME";
export const INSTALL_SCRIPT_PATH = "/install.sh" as const;
export const INSTALL_SCRIPT_HEADERS = {
Expand Down Expand Up @@ -192,66 +193,61 @@ resolve_managed_node_archive_suffix() {
}

resolve_managed_node_archive_name() {
platform_tag="$1"
archive_suffix="$(resolve_managed_node_archive_suffix "$platform_tag")"
archive_name="$(
curl -fsSL "$NODE_DIST_BASE_URL/SHASUMS256.txt" | while IFS= read -r line; do
case "$line" in
*" node-v"*"-$archive_suffix")
printf '%s\\n' "\${line#* }"
break
;;
esac
done
managed_node_platform_tag="$1"
managed_node_archive_suffix="$(resolve_managed_node_archive_suffix "$managed_node_platform_tag")"
managed_node_archive_name="$(
curl -fsSL "$NODE_DIST_BASE_URL/SHASUMS256.txt" \
| sed -n "s/^[[:xdigit:]]\\{64\\}[[:space:]][[:space:]]\\(node-v[^[:space:]]*-$managed_node_archive_suffix\\)$/\\1/p" \
| head -n 1
)"

if [ -z "$archive_name" ]; then
printf 'onequery installer: failed to resolve a managed Node.js 24.x archive for %s from %s\\n' "$platform_tag" "$NODE_DIST_BASE_URL" >&2
if [ -z "$managed_node_archive_name" ]; then
printf 'onequery installer: failed to resolve a managed Node.js 24.x archive for %s from %s\\n' "$managed_node_platform_tag" "$NODE_DIST_BASE_URL" >&2
exit 1
fi

printf '%s\\n' "$archive_name"
printf '%s\\n' "$managed_node_archive_name"
}

install_managed_node() {
install_dir="$1"
platform_tag="$2"
node_archive_name="$(resolve_managed_node_archive_name "$platform_tag")"
node_archive_path="$tmp_dir/$node_archive_name"
node_extract_root="$tmp_dir/node"
node_extract_dir="\${node_archive_name%.tar.gz}"
managed_node_dir="$install_dir/runtime/node"
managed_node_install_dir="$1"
managed_node_platform_tag="$2"
managed_node_archive_name="$(resolve_managed_node_archive_name "$managed_node_platform_tag")"
managed_node_archive_path="$tmp_dir/$managed_node_archive_name"
managed_node_extract_root="$tmp_dir/node"
managed_node_extract_dir="\${managed_node_archive_name%.tar.gz}"
managed_node_target_dir="$managed_node_install_dir/runtime/node"

printf 'Installing managed Node.js 24.x for onequery gateway...\\n'
rm -rf "$node_extract_root" "$managed_node_dir"
mkdir -p "$node_extract_root" "$install_dir/runtime"
curl -fsSL "$NODE_DIST_BASE_URL/$node_archive_name" -o "$node_archive_path"
tar -xzf "$node_archive_path" -C "$node_extract_root"
rm -rf "$managed_node_extract_root" "$managed_node_target_dir"
mkdir -p "$managed_node_extract_root" "$managed_node_install_dir/runtime"
curl -fsSL "$NODE_DIST_BASE_URL/$managed_node_archive_name" -o "$managed_node_archive_path"
tar -xzf "$managed_node_archive_path" -C "$managed_node_extract_root"

if [ ! -x "$node_extract_root/$node_extract_dir/bin/node" ]; then
if [ ! -x "$managed_node_extract_root/$managed_node_extract_dir/bin/node" ]; then
printf 'onequery installer: extracted managed Node.js archive is missing bin/node\\n' >&2
exit 1
fi

mv "$node_extract_root/$node_extract_dir" "$managed_node_dir"
mv "$managed_node_extract_root/$managed_node_extract_dir" "$managed_node_target_dir"
}

write_launcher() {
install_dir="$1"
target_triple="$2"
launcher_path="$install_dir/bin/onequery"
launcher_install_dir="$1"
launcher_target_triple="$2"
launcher_path="$launcher_install_dir/bin/onequery"

mkdir -p "$install_dir/bin"
mkdir -p "$launcher_install_dir/bin"
{
cat <<EOF
#!/bin/sh
set -eu
TARGET_TRIPLE="$target_triple"
TARGET_TRIPLE="$launcher_target_triple"
EOF
cat <<'EOF'
INSTALL_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
export ${ONEQUERY_RUNTIME_ROOT_ENV_VAR}="\${${ONEQUERY_RUNTIME_ROOT_ENV_VAR}:-$INSTALL_DIR/vendor/$TARGET_TRIPLE}"
managed_node_path="$INSTALL_DIR/$MANAGED_NODE_BIN_RELATIVE_PATH"
managed_node_path="$INSTALL_DIR/${MANAGED_NODE_BIN_RELATIVE_PATH}"
if [ -z "\${${PACKAGED_SERVER_JS_RUNTIME_ENV_VAR}:-}" ] && [ -x "$managed_node_path" ]; then
export ${PACKAGED_SERVER_JS_RUNTIME_ENV_VAR}="$managed_node_path"
fi
Expand Down