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:
hermes committed 2026-10-01 19:21:19 +00:00
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}`)
}
}