dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x
This commit is contained in:
commit
e6dcef9bf5
302 files changed
+75178
No files matched your search
@@ -0,0 +1,550 @@
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
|
||||
export const MANAGED_LIFECYCLE_MODULE = 'lib/dsh-docker-managed-lifecycle.js'
|
||||
export const PROFILE_PACKAGE_STORAGE_MODULE = 'lib/dsh-docker-profile-package-storage.js'
|
||||
const BIN_TARGET = 'lib/bin.js'
|
||||
const BIN_IMPORT = 'import { prepareManagedInvocation } from "./dsh-docker-managed-lifecycle.js";'
|
||||
const PROFILE_IMPORT = 'import { managedSigtermHandler, markManagedReady, provideManagedLifecycle } from "./dsh-docker-managed-lifecycle.js";'
|
||||
// parseDshArgs signature changed in dsh 0.2.0 (version and manageDesktopProfile come from runCli scope).
|
||||
const BIN_DISPATCHES = [
|
||||
'const invocation = parseDshArgs(process.argv.slice(2), readVersion());',
|
||||
'const invocation = parseDshArgs(process.argv.slice(2), version, manageDesktopProfile);',
|
||||
]
|
||||
const binDispatchPatched = (dispatch) => `${dispatch}\nconst managedExitCode = await prepareManagedInvocation(invocation);\nif (managedExitCode !== null) process.exit(managedExitCode);`
|
||||
// dsh 0.1.x indents the handler with 2 tabs, 0.2.x with 3 — match the shape, not the indent.
|
||||
const SIGTERM_SOURCE = /process\.on\("SIGTERM", \(\) => \{\n\t+interrupt\(0\);\n\t+\}\);/g
|
||||
const SIGTERM_PATCHED = 'process.on("SIGTERM", managedSigtermHandler(interrupt));'
|
||||
const HOST_PROVIDER = 'hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, options.environment);'
|
||||
const HOST_PROVIDER_PATCHED = `${HOST_PROVIDER}\n\t\tprovideManagedLifecycle(hostCtx);`
|
||||
// Same: settled-return indentation differs between dsh 0.1.x (1 tab) and 0.2.x (2 tabs).
|
||||
const PROFILE_RETURN_SOURCE = /(\t+)return \{\n\t+ctx,\n\t+shutdown\n\t+\};/g
|
||||
|
||||
const PROFILE_PACKAGE_STORAGE_SOURCE = String.raw`import {
|
||||
closeSync, cpSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync,
|
||||
statSync, unlinkSync, writeFileSync
|
||||
} from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { homedir } from "node:os";
|
||||
import { basename, dirname, isAbsolute, join, normalize } from "node:path";
|
||||
|
||||
const MODULES_BACKUP = ".dsh-platform-node_modules.previous";
|
||||
const WORKSPACE_BACKUP = ".dsh-platform-pnpm-workspace.previous";
|
||||
const MIGRATION_COMMIT = ".dsh-platform-profile-migration.committed";
|
||||
const PNPM_TIMEOUT_MS = 3e5;
|
||||
|
||||
function profileDirectory(profile) {
|
||||
if (typeof profile !== "string" || profile === "" || profile === "." || profile === ".."
|
||||
|| profile === "node_modules" || profile.includes("/") || profile.includes("\\")) {
|
||||
throw new Error("invalid managed DSH Profile name");
|
||||
}
|
||||
const dshHome = process.env.DSH_HOME || join(homedir(), ".dsh");
|
||||
return { dshHome, profileDir: join(dshHome, "profiles", profile) };
|
||||
}
|
||||
|
||||
function installedStore(profileDir) {
|
||||
const modulesState = join(profileDir, "node_modules", ".modules.yaml");
|
||||
if (!existsSync(modulesState)) return null;
|
||||
let value;
|
||||
try {
|
||||
value = JSON.parse(readFileSync(modulesState, "utf8"));
|
||||
} catch {
|
||||
throw new Error("Web Profile package metadata is not valid pnpm state");
|
||||
}
|
||||
if (typeof value?.storeDir !== "string" || !isAbsolute(value.storeDir)) {
|
||||
throw new Error("Web Profile package metadata has no absolute pnpm store");
|
||||
}
|
||||
return normalize(value.storeDir);
|
||||
}
|
||||
|
||||
function withStoreDirectory(source, storeRoot) {
|
||||
const line = "storeDir: " + JSON.stringify(storeRoot) + "\n";
|
||||
const matches = source.match(/^storeDir:[^\r\n]*(?:\r?\n|$)/gm) ?? [];
|
||||
if (matches.length > 1) throw new Error("Web Profile pnpm workspace has multiple storeDir entries");
|
||||
if (matches.length === 1) return source.replace(/^storeDir:[^\r\n]*(?:\r?\n|$)/m, line);
|
||||
return source.replace(/(?:\r?\n)?$/, "\n\n" + line);
|
||||
}
|
||||
|
||||
function replaceFile(path, content) {
|
||||
const temporary = path + ".dsh-platform-" + String(process.pid) + "-" + String(Date.now()) + ".tmp";
|
||||
try {
|
||||
writeFileSync(temporary, content, { flag: "wx", mode: statSync(path).mode & 0o777 });
|
||||
renameSync(temporary, path);
|
||||
} finally {
|
||||
try { unlinkSync(temporary); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function durableBackup(path, content, mode) {
|
||||
const handle = openSync(path, "wx", mode);
|
||||
try {
|
||||
writeFileSync(handle, content);
|
||||
fsyncSync(handle);
|
||||
} finally {
|
||||
closeSync(handle);
|
||||
}
|
||||
}
|
||||
|
||||
function restoreWorkspace(workspacePath, backupPath) {
|
||||
if (!existsSync(backupPath)) return;
|
||||
if (existsSync(workspacePath)) unlinkSync(workspacePath);
|
||||
renameSync(backupPath, workspacePath);
|
||||
}
|
||||
|
||||
function recoverInterruptedMigration(profileDir, workspacePath) {
|
||||
const modulesPath = join(profileDir, "node_modules");
|
||||
const modulesBackup = join(profileDir, MODULES_BACKUP);
|
||||
const workspaceBackup = join(profileDir, WORKSPACE_BACKUP);
|
||||
const commitMarker = join(profileDir, MIGRATION_COMMIT);
|
||||
if (!existsSync(modulesBackup) && !existsSync(workspaceBackup) && !existsSync(commitMarker)) return;
|
||||
if (existsSync(commitMarker)) {
|
||||
rmSync(workspaceBackup, { force: true });
|
||||
rmSync(modulesBackup, { recursive: true, force: true });
|
||||
rmSync(commitMarker, { force: true });
|
||||
return;
|
||||
}
|
||||
if (!existsSync(modulesBackup)) {
|
||||
restoreWorkspace(workspacePath, workspaceBackup);
|
||||
return;
|
||||
}
|
||||
if (existsSync(modulesPath)) rmSync(modulesPath, { recursive: true, force: true });
|
||||
if (existsSync(modulesBackup)) renameSync(modulesBackup, modulesPath);
|
||||
restoreWorkspace(workspacePath, workspaceBackup);
|
||||
}
|
||||
|
||||
function pnpmInstall(profileDir, offline) {
|
||||
const arguments_ = ["install"];
|
||||
if (offline) arguments_.push("--offline");
|
||||
arguments_.push("--frozen-lockfile", "--reporter=append-only");
|
||||
return spawnSync("pnpm", arguments_, {
|
||||
cwd: profileDir,
|
||||
encoding: "utf8",
|
||||
env: process.env,
|
||||
timeout: PNPM_TIMEOUT_MS,
|
||||
maxBuffer: 8 * 1024 * 1024
|
||||
});
|
||||
}
|
||||
|
||||
function failureDetail(result) {
|
||||
if (result.error !== void 0) return result.error.message;
|
||||
const output = [result.stdout, result.stderr].filter((value) => typeof value === "string" && value.trim() !== "").join("\n");
|
||||
return output.trim().slice(-4096) || "pnpm exited with status " + String(result.status);
|
||||
}
|
||||
|
||||
function migrateProfile(profileDir, workspacePath, originalWorkspace, currentStore, storeRoot) {
|
||||
const modulesPath = join(profileDir, "node_modules");
|
||||
const modulesBackup = join(profileDir, MODULES_BACKUP);
|
||||
const workspaceBackup = join(profileDir, WORKSPACE_BACKUP);
|
||||
const commitMarker = join(profileDir, MIGRATION_COMMIT);
|
||||
const targetStore = join(storeRoot, basename(currentStore));
|
||||
let copiedStore = false;
|
||||
let migratedStore;
|
||||
try {
|
||||
if (existsSync(currentStore) && statSync(currentStore).isDirectory()) {
|
||||
try {
|
||||
mkdirSync(targetStore, { recursive: true });
|
||||
cpSync(currentStore, targetStore, { recursive: true, force: false, errorOnExist: false });
|
||||
copiedStore = true;
|
||||
} catch {}
|
||||
}
|
||||
durableBackup(workspaceBackup, originalWorkspace, statSync(workspacePath).mode & 0o777);
|
||||
replaceFile(workspacePath, withStoreDirectory(originalWorkspace, storeRoot));
|
||||
renameSync(modulesPath, modulesBackup);
|
||||
let result = pnpmInstall(profileDir, copiedStore);
|
||||
if (result.status !== 0 && copiedStore) {
|
||||
rmSync(modulesPath, { recursive: true, force: true });
|
||||
result = pnpmInstall(profileDir, false);
|
||||
}
|
||||
if (result.status !== 0) throw new Error(failureDetail(result));
|
||||
migratedStore = installedStore(profileDir);
|
||||
if (migratedStore === null || dirname(migratedStore) !== normalize(storeRoot)) {
|
||||
throw new Error("pnpm rebuilt the Profile with an unexpected store");
|
||||
}
|
||||
durableBackup(commitMarker, "committed\n", 384);
|
||||
} catch (error) {
|
||||
if (existsSync(modulesPath)) rmSync(modulesPath, { recursive: true, force: true });
|
||||
if (existsSync(modulesBackup)) renameSync(modulesBackup, modulesPath);
|
||||
restoreWorkspace(workspacePath, workspaceBackup);
|
||||
rmSync(commitMarker, { force: true });
|
||||
throw new Error("failed to migrate Profile package storage: " + String(error?.message ?? error));
|
||||
}
|
||||
// The new layout is committed after verification. Cleanup is recoverable:
|
||||
// removing the workspace backup first ensures any surviving modules backup
|
||||
// is recognized as a committed migration on the next launch.
|
||||
rmSync(workspaceBackup, { force: true });
|
||||
rmSync(modulesBackup, { recursive: true, force: true });
|
||||
rmSync(commitMarker, { force: true });
|
||||
return migratedStore;
|
||||
}
|
||||
|
||||
export function prepareProfilePackageStorage(profile) {
|
||||
const { dshHome, profileDir } = profileDirectory(profile);
|
||||
const manifestPath = join(profileDir, "package.json");
|
||||
const workspacePath = join(profileDir, "pnpm-workspace.yaml");
|
||||
if (!existsSync(manifestPath) || !existsSync(workspacePath)) return Object.freeze({ status: "absent" });
|
||||
const storeRoot = join(dshHome, ".pnpm-store");
|
||||
recoverInterruptedMigration(profileDir, workspacePath);
|
||||
const currentStore = installedStore(profileDir);
|
||||
if (currentStore !== null && dirname(currentStore) !== normalize(storeRoot)) {
|
||||
const migratedStore = migrateProfile(
|
||||
profileDir, workspacePath, readFileSync(workspacePath, "utf8"), currentStore, storeRoot
|
||||
);
|
||||
console.log("dsh: migrated Profile package storage from " + currentStore + " to " + migratedStore);
|
||||
return Object.freeze({ status: "migrated", currentStore: migratedStore, storeRoot });
|
||||
}
|
||||
const original = readFileSync(workspacePath, "utf8");
|
||||
const updated = withStoreDirectory(original, storeRoot);
|
||||
if (updated !== original) replaceFile(workspacePath, updated);
|
||||
return Object.freeze({ status: "ready", currentStore, storeRoot });
|
||||
}
|
||||
`;
|
||||
|
||||
const ADAPTER_SOURCE = String.raw`import { existsSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
||||
import { request } from "node:http";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { prepareProfilePackageStorage } from "./dsh-docker-profile-package-storage.js";
|
||||
|
||||
const API_PREFIX = "/_dsh_platform/api/v1/";
|
||||
const REQUEST_TIMEOUT_MS = 3e3;
|
||||
const RESTART_FALLBACK_MS = 10e3;
|
||||
let managedSessionId = null;
|
||||
|
||||
function packageManifest(root, packageName) {
|
||||
if (packageName.includes("\\")) return null;
|
||||
const parts = packageName.split("/");
|
||||
const validShape = packageName.startsWith("@") ? parts.length === 2 : parts.length === 1;
|
||||
if (!validShape || parts.some((part) => part === "" || part === "." || part === "..")) {
|
||||
return null;
|
||||
}
|
||||
return join(root, "node_modules", ...parts, "package.json");
|
||||
}
|
||||
|
||||
function packageExists(root, packageName) {
|
||||
const manifest = packageManifest(root, packageName);
|
||||
return manifest !== null && existsSync(manifest);
|
||||
}
|
||||
|
||||
function repairOrphanedProfileBundles() {
|
||||
const profileDir = join(process.env.DSH_HOME || join(homedir(), ".dsh"), "profiles", "web");
|
||||
const manifestPath = join(profileDir, "package.json");
|
||||
if (!existsSync(manifestPath)) return;
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
const bundles = manifest?.dsh?.profile?.bundles;
|
||||
if (!Array.isArray(bundles)) return;
|
||||
const dependencies = new Set(Object.keys(manifest.dependencies ?? {}));
|
||||
const dshRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const kept = [];
|
||||
const removed = [];
|
||||
for (const packageName of bundles) {
|
||||
if (typeof packageName !== "string"
|
||||
|| dependencies.has(packageName)
|
||||
|| packageExists(profileDir, packageName)
|
||||
|| packageExists(dshRoot, packageName)) {
|
||||
kept.push(packageName);
|
||||
} else {
|
||||
removed.push(packageName);
|
||||
}
|
||||
}
|
||||
if (removed.length === 0) return;
|
||||
manifest.dsh = { ...manifest.dsh, profile: { ...manifest.dsh.profile, bundles: kept } };
|
||||
const temporary = manifestPath + ".dsh-platform-" + String(process.pid) + "-" + String(Date.now()) + ".tmp";
|
||||
try {
|
||||
writeFileSync(temporary, JSON.stringify(manifest, null, 2) + "\n", {
|
||||
flag: "wx",
|
||||
mode: statSync(manifestPath).mode & 0o777
|
||||
});
|
||||
renameSync(temporary, manifestPath);
|
||||
} finally {
|
||||
try { unlinkSync(temporary); } catch {}
|
||||
}
|
||||
console.log("dsh: removed orphaned profile bundle(s): " + removed.join(", "));
|
||||
}
|
||||
|
||||
function socketPath(name) {
|
||||
return process.env.DSH_PLATFORM_RUN + "/" + name;
|
||||
}
|
||||
|
||||
function requestJson(socket, method, path, body) {
|
||||
return new Promise((resolveRequest, reject) => {
|
||||
const bytes = body === void 0 ? void 0 : Buffer.from(JSON.stringify(body));
|
||||
const req = request({
|
||||
socketPath: socket,
|
||||
method,
|
||||
path,
|
||||
headers: bytes === void 0 ? {} : {
|
||||
"content-type": "application/json",
|
||||
"content-length": bytes.byteLength
|
||||
}
|
||||
}, (response) => {
|
||||
const chunks = [];
|
||||
response.on("data", (chunk) => chunks.push(chunk));
|
||||
response.on("end", () => {
|
||||
let value = {};
|
||||
try {
|
||||
value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
||||
} catch {}
|
||||
if ((response.statusCode ?? 500) >= 400) {
|
||||
const error = new Error(value.error ?? "local API request failed");
|
||||
error.statusCode = response.statusCode;
|
||||
reject(error);
|
||||
} else resolveRequest(value);
|
||||
});
|
||||
});
|
||||
req.setTimeout(REQUEST_TIMEOUT_MS, () => req.destroy(new Error("local API request timed out")));
|
||||
req.once("error", reject);
|
||||
req.end(bytes);
|
||||
});
|
||||
}
|
||||
|
||||
function managedWeb(invocation) {
|
||||
return process.env.DSH_PLATFORM_MANAGED === "1"
|
||||
&& invocation?.mode === "profile"
|
||||
&& invocation.profile === "web";
|
||||
}
|
||||
|
||||
async function management(method, path) {
|
||||
return requestJson(socketPath("management-cli.sock"), method, API_PREFIX + path);
|
||||
}
|
||||
|
||||
export async function prepareManagedInvocation(invocation) {
|
||||
if (process.env.DSH_PLATFORM_MANAGED === "1" && invocation?.mode === "plugin") {
|
||||
try {
|
||||
prepareProfilePackageStorage(invocation.profile);
|
||||
} catch (error) {
|
||||
console.error("dsh: failed to prepare Profile package storage: " + String(error?.message ?? error));
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!managedWeb(invocation)) return null;
|
||||
if (typeof process.env.DSH_PLATFORM_RUN !== "string" || process.env.DSH_PLATFORM_RUN === "") {
|
||||
console.error("dsh: managed lifecycle runtime directory is unavailable");
|
||||
return 1;
|
||||
}
|
||||
let claimed;
|
||||
try {
|
||||
claimed = await requestJson(socketPath("dsh-lifecycle.sock"), "POST", "/v1/runtime/claim", {
|
||||
launchToken: process.env.DSH_PLATFORM_LAUNCH_TOKEN ?? ""
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.statusCode !== 409) {
|
||||
console.error("dsh: managed lifecycle broker is unavailable; refusing an unsupervised Web instance");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (claimed !== void 0) {
|
||||
if (typeof claimed.sessionId !== "string" || claimed.sessionId === "") {
|
||||
console.error("dsh: managed lifecycle broker returned an invalid launch session");
|
||||
return 1;
|
||||
}
|
||||
managedSessionId = claimed.sessionId;
|
||||
delete process.env.DSH_PLATFORM_LAUNCH_TOKEN;
|
||||
try {
|
||||
prepareProfilePackageStorage("web");
|
||||
} catch (error) {
|
||||
console.error("dsh: Web Profile package storage repair failed; plugin changes may be unavailable: " + String(error?.message ?? error));
|
||||
}
|
||||
try {
|
||||
repairOrphanedProfileBundles();
|
||||
} catch (error) {
|
||||
console.error("dsh: failed to repair orphaned Web Profile bundles: " + String(error?.message ?? error));
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let status;
|
||||
try {
|
||||
status = await management("GET", "status");
|
||||
} catch {
|
||||
console.error("dsh: DSH is managed by the container platform, but its Control Plane is unavailable");
|
||||
console.error("dsh: open /_dsh_platform/console/ after the Control Plane recovers");
|
||||
return 1;
|
||||
}
|
||||
const state = status?.dshLifecycle?.state;
|
||||
if (state === "stopped") {
|
||||
try {
|
||||
const task = await management("POST", "start-dsh");
|
||||
console.log("dsh: requested managed DSH start (task " + String(task.taskId) + ")");
|
||||
return 0;
|
||||
} catch (error) {
|
||||
console.error("dsh: managed DSH start was rejected: " + String(error?.message ?? error));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (state === "failed" || status?.recoveryMode != null) {
|
||||
console.error("dsh: managed DSH is in recovery mode; open /_dsh_platform/console/");
|
||||
return 1;
|
||||
}
|
||||
if (["running", "starting", "stopping", "restarting", "recovering"].includes(state)) {
|
||||
console.log("dsh: managed DSH is " + state + "; no second Web instance was started");
|
||||
return 0;
|
||||
}
|
||||
console.error("dsh: managed DSH state is unavailable; open /_dsh_platform/console/");
|
||||
return 1;
|
||||
}
|
||||
|
||||
export function managedSigtermHandler(interrupt) {
|
||||
if (managedSessionId === null) return () => interrupt(0);
|
||||
let count = 0;
|
||||
let fallback;
|
||||
let abandoned = false;
|
||||
return () => {
|
||||
count += 1;
|
||||
if (count > 1) {
|
||||
abandoned = true;
|
||||
if (fallback !== void 0) clearTimeout(fallback);
|
||||
interrupt(0);
|
||||
return;
|
||||
}
|
||||
void requestJson(socketPath("dsh-lifecycle.sock"), "POST", "/v1/runtime/signal", {
|
||||
sessionId: managedSessionId,
|
||||
signal: "SIGTERM"
|
||||
}).then(async ({ disposition }) => {
|
||||
if (abandoned) return;
|
||||
if (disposition === "terminate") {
|
||||
interrupt(0);
|
||||
return;
|
||||
}
|
||||
if (disposition !== "request-restart") throw new Error("invalid lifecycle signal response");
|
||||
const task = await management("POST", "restart-dsh");
|
||||
console.log("dsh: requested managed DSH restart (task " + String(task.taskId) + ")");
|
||||
fallback = setTimeout(() => interrupt(0), RESTART_FALLBACK_MS);
|
||||
fallback.unref?.();
|
||||
}).catch(() => interrupt(0));
|
||||
};
|
||||
}
|
||||
|
||||
export async function markManagedReady(ctx) {
|
||||
if (managedSessionId === null) return;
|
||||
const connection = ctx?.get?.("connection");
|
||||
const webServer = ctx?.get?.("webServer");
|
||||
const readyUrl = typeof connection?.authenticatedUrl === "function"
|
||||
&& Number.isInteger(webServer?.port)
|
||||
? connection.authenticatedUrl("http://127.0.0.1:" + String(webServer.port))
|
||||
: null;
|
||||
await requestJson(socketPath("dsh-lifecycle.sock"), "POST", "/v1/runtime/ready", {
|
||||
sessionId: managedSessionId,
|
||||
readyUrl
|
||||
});
|
||||
}
|
||||
|
||||
export function provideManagedLifecycle(hostCtx) {
|
||||
if (managedSessionId === null) return;
|
||||
hostCtx.provide("dshPlatformLifecycle", Object.freeze({
|
||||
restart: () => management("POST", "restart-dsh")
|
||||
}));
|
||||
}
|
||||
`
|
||||
|
||||
function exactlyOnce(source, pattern, name, path) {
|
||||
const matches = [...source.matchAll(pattern)]
|
||||
if (matches.length !== 1) {
|
||||
throw new Error(`Expected exactly one ${name} in ${path}, found ${String(matches.length)}. The upstream package may have changed; review this patch before building.`)
|
||||
}
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
function sigtermSource(source) {
|
||||
const matches = [...source.matchAll(SIGTERM_SOURCE)]
|
||||
return matches.length === 1 ? matches[0] : null
|
||||
}
|
||||
|
||||
function replaceOnce(source, before, after, name, path) {
|
||||
const count = source.split(before).length - 1
|
||||
if (count !== 1) {
|
||||
throw new Error(`Expected exactly one ${name} in ${path}, found ${String(count)}. The upstream package may have changed; review this patch before building.`)
|
||||
}
|
||||
return source.replace(before, after)
|
||||
}
|
||||
|
||||
function profileImplementation(dshRoot, binSource) {
|
||||
// dsh 0.1.x: hashed chunk import; 0.2.x: bare facade ./profile-boot.js (any quote style).
|
||||
const binMatch = exactlyOnce(
|
||||
binSource,
|
||||
/const \{ runProfile \} = await import\(['"](\.\/profile-boot(?:-[A-Za-z0-9_-]+)?\.js)['"]\);/g,
|
||||
'dynamic Web Profile import',
|
||||
join(dshRoot, BIN_TARGET),
|
||||
)
|
||||
const wrapperPath = join(dshRoot, 'lib', basename(binMatch[1]))
|
||||
const wrapper = readFileSync(wrapperPath, 'utf8')
|
||||
if (sigtermSource(wrapper) !== null && wrapper.includes(HOST_PROVIDER)) return wrapperPath
|
||||
const implementation = exactlyOnce(
|
||||
wrapper,
|
||||
/import \{[^\n;]*\brunProfile\b[^\n;]*\} from ['"](\.\/profile-boot(?:-[A-Za-z0-9_-]+)?\.js)['"];?/g,
|
||||
'Profile implementation import',
|
||||
wrapperPath,
|
||||
)
|
||||
return join(dshRoot, 'lib', basename(implementation[1]))
|
||||
}
|
||||
|
||||
export function applyPatch(dshRoot) {
|
||||
const root = resolve(dshRoot)
|
||||
const binPath = join(root, BIN_TARGET)
|
||||
const bin = readFileSync(binPath, 'utf8')
|
||||
const profilePath = profileImplementation(root, bin)
|
||||
let profile = readFileSync(profilePath, 'utf8')
|
||||
if (readFileSync(binPath, 'utf8').includes(BIN_IMPORT)) throw new Error('Managed lifecycle Patch is already applied')
|
||||
if (existsSync(join(root, MANAGED_LIFECYCLE_MODULE))) throw new Error('Managed lifecycle adapter path already exists')
|
||||
if (existsSync(join(root, PROFILE_PACKAGE_STORAGE_MODULE))) throw new Error('Profile package storage adapter path already exists')
|
||||
exactlyOnce(bin, /^#!\/usr\/bin\/env node$/gm, 'DSH bin shebang', binPath)
|
||||
let patchedBin = bin.replace(/^#!\/usr\/bin\/env node$/m, `#!/usr/bin/env node\n${BIN_IMPORT}`)
|
||||
const dispatch = BIN_DISPATCHES.find(candidate => patchedBin.includes(candidate))
|
||||
if (dispatch === undefined) {
|
||||
throw new Error(`Expected one known invocation dispatch in ${binPath}; the upstream package may have changed. Known: ${JSON.stringify(BIN_DISPATCHES)}`)
|
||||
}
|
||||
patchedBin = replaceOnce(patchedBin, dispatch, binDispatchPatched(dispatch), 'DSH invocation dispatch', binPath)
|
||||
profile = `${PROFILE_IMPORT}\n${profile}`
|
||||
const sigterm = sigtermSource(profile)
|
||||
if (sigterm === null) throw new Error(`Expected exactly one Web Profile SIGTERM handler in ${profilePath}, found 0. The upstream package may have changed; review this patch before building.`)
|
||||
profile = profile.replace(SIGTERM_SOURCE, SIGTERM_PATCHED)
|
||||
profile = replaceOnce(profile, HOST_PROVIDER, HOST_PROVIDER_PATCHED, 'Web Profile host provider', profilePath)
|
||||
// Settled return: preserve the source indentation, insert the ready-marker before it.
|
||||
const settledMatches = [...profile.matchAll(PROFILE_RETURN_SOURCE)]
|
||||
if (settledMatches.length !== 1) {
|
||||
throw new Error(`Expected exactly one Web Profile settled return in ${profilePath}, found ${String(settledMatches.length)}. The upstream package may have changed; review this patch before building.`)
|
||||
}
|
||||
const settled = settledMatches[0]
|
||||
profile = profile.replace(
|
||||
PROFILE_RETURN_SOURCE,
|
||||
(match, indent) => `${indent}if (ctx.get("loader") !== void 0) await markManagedReady(ctx);\n${match}`,
|
||||
)
|
||||
writeFileSync(join(root, MANAGED_LIFECYCLE_MODULE), ADAPTER_SOURCE)
|
||||
writeFileSync(join(root, PROFILE_PACKAGE_STORAGE_MODULE), PROFILE_PACKAGE_STORAGE_SOURCE)
|
||||
writeFileSync(profilePath, profile)
|
||||
writeFileSync(binPath, patchedBin)
|
||||
}
|
||||
|
||||
export function verifyPatch(dshRoot) {
|
||||
const root = resolve(dshRoot)
|
||||
const binPath = join(root, BIN_TARGET)
|
||||
const bin = readFileSync(binPath, 'utf8')
|
||||
const profilePath = profileImplementation(root, bin)
|
||||
const profile = readFileSync(profilePath, 'utf8')
|
||||
const adapter = readFileSync(join(root, MANAGED_LIFECYCLE_MODULE), 'utf8')
|
||||
const packageStorage = readFileSync(join(root, PROFILE_PACKAGE_STORAGE_MODULE), 'utf8')
|
||||
const dispatched = BIN_DISPATCHES.filter(candidate => bin.split(binDispatchPatched(candidate)).length - 1 === 1)
|
||||
if (bin.split(BIN_IMPORT).length - 1 !== 1 || dispatched.length !== 1) {
|
||||
throw new Error(`Managed lifecycle bin Patch verification failed for ${binPath}`)
|
||||
}
|
||||
if (profile.split(PROFILE_IMPORT).length - 1 !== 1
|
||||
|| profile.split(SIGTERM_PATCHED).length - 1 !== 1
|
||||
|| profile.split(HOST_PROVIDER_PATCHED).length - 1 !== 1
|
||||
|| [...profile.matchAll(/\t+if \(ctx\.get\("loader"\) !== void 0\) await markManagedReady\(ctx\);\n\t+return \{\n\t+ctx,\n\t+shutdown\n\t+\};/g)].length !== 1
|
||||
|| sigtermSource(profile) !== null) {
|
||||
throw new Error(`Managed lifecycle Profile Patch verification failed for ${profilePath}`)
|
||||
}
|
||||
for (const marker of ['prepareManagedInvocation', 'managedSigtermHandler', 'markManagedReady', 'provideManagedLifecycle']) {
|
||||
if (!adapter.includes(`export function ${marker}`) && !adapter.includes(`export async function ${marker}`)) {
|
||||
throw new Error(`Managed lifecycle adapter is missing ${marker}`)
|
||||
}
|
||||
}
|
||||
if (!adapter.includes('function repairOrphanedProfileBundles()')) {
|
||||
throw new Error('Managed lifecycle adapter is missing repairOrphanedProfileBundles')
|
||||
}
|
||||
for (const marker of ['prepareProfilePackageStorage', 'migrateProfile', '.pnpm-store']) {
|
||||
if (!packageStorage.includes(marker)) throw new Error(`Managed lifecycle Profile package storage is missing ${marker}`)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user