fix(release): repair Windows prerelease auth smoke (#6682)

This commit is contained in:
Ray Xi
2026-08-10 08:43:56 +00:00
committed by GitHub
parent a0474c540e
commit f7a3fe3662
5 changed files with 182 additions and 74 deletions
+39 -6
View File
@@ -187,6 +187,13 @@ export async function installPackedWinApp(config: ToolPackConfig): Promise<WinIn
await invokeNsis(paths, paths.setupPath, installArgs(config, paths), "install");
}));
if (!(await pathExists(paths.installedExePath))) throw new Error(`installer completed but executable is missing at ${paths.installedExePath}`);
// Portable shipping builds omit namespaceBaseRoot so end users fall back to
// Electron userData. The tools-pack installed copy must retain its isolated
// runtime root for OS protocol cold launches, which inherit none of the
// OD_PACKAGED_CONFIG_PATH environment used by `tools-pack win start`.
await measureLifecycleStep(lifecycleTimings, "pin installed packaged namespace", async () => {
await pinInstalledPackagedConfigNamespace(config, paths.installedExePath);
});
const registryEntries = await measureLifecycleStep(lifecycleTimings, "query registry", async () => queryWinRegistryEntries(paths, config));
const installPayload = await measureLifecycleStep(lifecycleTimings, "collect payload report", async () => collectInstallPayloadReport(paths));
await measureLifecycleStep(lifecycleTimings, "write install marker", async () => writeJsonMarker(paths.installMarkerPath, {
@@ -214,16 +221,42 @@ export async function installPackedWinApp(config: ToolPackConfig): Promise<WinIn
};
}
async function writeInstalledLaunchPackagedConfig(config: ToolPackConfig, executablePath: string): Promise<string> {
/**
* Pin the tools-pack runtime namespace into the installed app's packaged config
* and write the launch override used by `tools-pack win start`.
*
* The installed config is the only source available to a bare executable
* launched through the Windows protocol registry. Keeping both copies
* identical prevents that cold launch from resolving a different daemon data
* root than the process started by tools-pack.
*/
async function pinInstalledPackagedConfigNamespace(
config: ToolPackConfig,
executablePath: string,
): Promise<{ installedConfigPath: string; launchConfigPath: string }> {
const installedConfigPath = join(dirname(executablePath), "resources", "open-design-config.json");
if (!(await pathExists(installedConfigPath))) {
throw new Error(`installed packaged config missing at ${installedConfigPath}`);
}
const raw = JSON.parse(await readFile(installedConfigPath, "utf8")) as Record<string, unknown>;
if (typeof raw !== "object" || raw == null || Array.isArray(raw)) {
throw new Error(`installed packaged config must be a JSON object: ${installedConfigPath}`);
}
const pinned = {
...raw,
namespace: config.namespace,
namespaceBaseRoot: config.roots.runtime.namespaceBaseRoot,
};
const body = `${JSON.stringify(pinned, null, 2)}\n`;
await writeFile(installedConfigPath, body, "utf8");
const launchConfigPath = join(config.roots.runtime.namespaceRoot, "runtime", "launch-open-design-config.json");
await mkdir(dirname(launchConfigPath), { recursive: true });
await writeFile(
launchConfigPath,
`${JSON.stringify({ ...raw, namespaceBaseRoot: config.roots.runtime.namespaceBaseRoot }, null, 2)}\n`,
"utf8",
);
await writeFile(launchConfigPath, body, "utf8");
return { installedConfigPath, launchConfigPath };
}
async function writeInstalledLaunchPackagedConfig(config: ToolPackConfig, executablePath: string): Promise<string> {
const { launchConfigPath } = await pinInstalledPackagedConfigNamespace(config, executablePath);
return launchConfigPath;
}
+38 -1
View File
@@ -1,4 +1,4 @@
import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
@@ -110,6 +110,40 @@ async function writeFakeUnpackedExe(config: ToolPackConfig): Promise<void> {
}
describe("installPackedWinApp", () => {
it("pins the installed portable config to the tools-pack namespace for bare protocol launches", async () => {
const root = await mkdtemp(join(tmpdir(), "open-design-win-lifecycle-"));
const config = { ...createConfig(root), portable: true };
const paths = resolveWinPaths(config);
const installedConfigPath = join(paths.installDir, "resources", "open-design-config.json");
try {
await mkdir(dirname(paths.setupPath), { recursive: true });
await writeFile(paths.setupPath, "", "utf8");
invokeNsis.mockReset();
invokeNsis.mockImplementation(async () => {
await mkdir(dirname(installedConfigPath), { recursive: true });
await writeFile(paths.installedExePath, "", "utf8");
await writeFile(
installedConfigPath,
`${JSON.stringify({ channel: "prerelease", namespace: "baked-default" }, null, 2)}\n`,
"utf8",
);
});
const result = await installPackedWinApp(config);
const installedConfig = JSON.parse(await readFile(installedConfigPath, "utf8")) as Record<string, unknown>;
expect(installedConfig).toMatchObject({
channel: "prerelease",
namespace: config.namespace,
namespaceBaseRoot: config.roots.runtime.namespaceBaseRoot,
});
expect(result.lifecycleTimings.map(({ step }) => step)).toContain("pin installed packaged namespace");
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("creates the exact fresh install directory before invoking transactional NSIS", async () => {
const root = await mkdtemp(join(tmpdir(), "open-design-win-lifecycle-"));
const config = createConfig(root);
@@ -121,7 +155,10 @@ describe("installPackedWinApp", () => {
invokeNsis.mockReset();
invokeNsis.mockImplementation(async () => {
await expect(access(paths.installDir)).resolves.toBeUndefined();
const installedConfigPath = join(paths.installDir, "resources", "open-design-config.json");
await mkdir(dirname(installedConfigPath), { recursive: true });
await writeFile(paths.installedExePath, "", "utf8");
await writeFile(installedConfigPath, "{}\n", "utf8");
});
const result = await installPackedWinApp(config);