mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
Fix packaged beta build resources
This commit is contained in:
parent
cba8bf151d
commit
e0c76a09f2
8 changed files with 209 additions and 5 deletions
|
|
@ -1023,6 +1023,11 @@ const PROMPT_TEMPLATES_DIR = resolveDaemonResourceDir(
|
|||
'prompt-templates',
|
||||
path.join(PROJECT_ROOT, 'prompt-templates'),
|
||||
);
|
||||
const BUNDLED_PLUGINS_DIR = resolveDaemonResourceDir(
|
||||
DAEMON_RESOURCE_ROOT,
|
||||
path.join('plugins', '_official'),
|
||||
defaultBundledRoot(PROJECT_ROOT),
|
||||
);
|
||||
export function resolveDataDir(raw, projectRoot) {
|
||||
if (!raw) return path.join(projectRoot, '.od');
|
||||
// expandHomePrefix is shared with media-config.ts so OD_DATA_DIR and
|
||||
|
|
@ -2635,14 +2640,15 @@ export async function startServer({
|
|||
}
|
||||
|
||||
// Plan §3.I3 / spec §23.3.5 — register every plugin under
|
||||
// <projectRoot>/plugins/_official/** as a bundled plugin. The walker
|
||||
// <resourceRoot>/plugins/_official/** in packaged runs, or
|
||||
// <projectRoot>/plugins/_official/** in workspace runs, as bundled plugins. The walker
|
||||
// is idempotent (upserts on every boot) so a daemon upgrade rotates
|
||||
// the bundled set in lockstep with the code. ENOENT is silent —
|
||||
// running the daemon outside the dev tree just skips this step.
|
||||
try {
|
||||
const result = await registerBundledPlugins({
|
||||
db,
|
||||
bundledRoot: defaultBundledRoot(PROJECT_ROOT),
|
||||
bundledRoot: BUNDLED_PLUGINS_DIR,
|
||||
});
|
||||
if (result.registered.length > 0) {
|
||||
console.log(`[plugins] registered ${result.registered.length} bundled plugin(s)`);
|
||||
|
|
|
|||
|
|
@ -557,6 +557,73 @@ function isMacCodeBundle(name) {
|
|||
return name.endsWith(".app") || name.endsWith(".framework");
|
||||
}
|
||||
|
||||
async function ensureRelativeSymlink(linkPath, targetPath, type) {
|
||||
if (await pathLstatExists(linkPath)) {
|
||||
const metadata = await lstat(linkPath);
|
||||
if (metadata.isSymbolicLink()) {
|
||||
const existingTarget = await readlink(linkPath);
|
||||
if (existingTarget === targetPath) return false;
|
||||
}
|
||||
await rm(linkPath, { force: true, recursive: true });
|
||||
}
|
||||
|
||||
await symlink(targetPath, linkPath, type);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function normalizeMacVersionedFramework(frameworkPath) {
|
||||
const versionsRoot = path.join(frameworkPath, "Versions");
|
||||
const entries = await readdir(versionsRoot, { withFileTypes: true }).catch(() => []);
|
||||
const versionName = entries
|
||||
.filter((entry) => entry.isDirectory() && entry.name !== "Current")
|
||||
.map((entry) => entry.name)
|
||||
.sort()[0];
|
||||
if (versionName == null) return false;
|
||||
|
||||
const versionPath = path.join(versionsRoot, versionName);
|
||||
await ensureRelativeSymlink(path.join(versionsRoot, "Current"), versionName, "dir");
|
||||
|
||||
const versionEntries = await readdir(versionPath, { withFileTypes: true }).catch(() => []);
|
||||
let changed = false;
|
||||
for (const entry of versionEntries) {
|
||||
if (entry.name === "_CodeSignature") continue;
|
||||
const targetPath = `Versions/Current/${entry.name}`;
|
||||
const linkPath = path.join(frameworkPath, entry.name);
|
||||
const type = entry.isDirectory() ? "dir" : "file";
|
||||
changed = (await ensureRelativeSymlink(linkPath, targetPath, type)) || changed;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
async function normalizeMacVersionedFrameworks(appPath) {
|
||||
const frameworksRoot = path.join(appPath, "Contents", "Frameworks");
|
||||
|
||||
async function visit(current) {
|
||||
const entries = await readdir(current, { withFileTypes: true }).catch(() => []);
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const entryPath = path.join(current, entry.name);
|
||||
if (entry.name.endsWith(".framework")) {
|
||||
await normalizeMacVersionedFramework(entryPath);
|
||||
continue;
|
||||
}
|
||||
await visit(entryPath);
|
||||
}
|
||||
}
|
||||
|
||||
await visit(frameworksRoot);
|
||||
}
|
||||
|
||||
async function resolveMacAdhocSignTarget(bundlePath, bundleName) {
|
||||
if (!bundleName.endsWith(".framework")) return bundlePath;
|
||||
|
||||
const currentVersionPath = path.join(bundlePath, "Versions", "Current");
|
||||
if (await pathExists(currentVersionPath)) return currentVersionPath;
|
||||
|
||||
return bundlePath;
|
||||
}
|
||||
|
||||
async function collectMacAdhocSignTargets(appPath) {
|
||||
const frameworksRoot = path.join(appPath, "Contents", "Frameworks");
|
||||
const targets = [];
|
||||
|
|
@ -567,7 +634,7 @@ async function collectMacAdhocSignTargets(appPath) {
|
|||
if (!entry.isDirectory()) continue;
|
||||
const entryPath = path.join(current, entry.name);
|
||||
if (isMacCodeBundle(entry.name)) {
|
||||
targets.push(entryPath);
|
||||
targets.push(await resolveMacAdhocSignTarget(entryPath, entry.name));
|
||||
continue;
|
||||
}
|
||||
await visit(entryPath);
|
||||
|
|
@ -580,6 +647,7 @@ async function collectMacAdhocSignTargets(appPath) {
|
|||
}
|
||||
|
||||
async function signMacAdhocBundle(appPath) {
|
||||
await normalizeMacVersionedFrameworks(appPath);
|
||||
const targets = await collectMacAdhocSignTargets(appPath);
|
||||
for (const target of targets) {
|
||||
await execFileAsync("codesign", ["--force", "--sign", "-", "--timestamp=none", target], {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ async function buildWorkspaceArtifacts(config: ToolPackConfig): Promise<void> {
|
|||
await runPnpm(config, ["--filter", "@open-design/sidecar-proto", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/sidecar", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/platform", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/agui-adapter", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/plugin-runtime", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/daemon", "build"]);
|
||||
try {
|
||||
await runPnpm(config, ["--filter", "@open-design/web", "build"], {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ const BUNDLED_RESOURCE_TREES = [
|
|||
{ from: "design-templates", to: "design-templates" },
|
||||
{ from: "design-systems", to: "design-systems" },
|
||||
{ from: "craft", to: "craft" },
|
||||
{ from: join("plugins", "_official"), to: join("plugins", "_official") },
|
||||
{ from: join("assets", "frames"), to: "frames" },
|
||||
{ from: join("assets", "community-pets"), to: "community-pets" },
|
||||
{ from: "prompt-templates", to: "prompt-templates" },
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ async function buildWorkspaceArtifacts(config: ToolPackConfig): Promise<void> {
|
|||
await runPnpm(config, ["--filter", "@open-design/sidecar-proto", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/sidecar", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/platform", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/agui-adapter", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/plugin-runtime", "build"]);
|
||||
await runPnpm(config, ["--filter", "@open-design/daemon", "build"]);
|
||||
try {
|
||||
await runPnpm(config, ["--filter", "@open-design/web", "build"], { OD_WEB_OUTPUT_MODE: config.webOutputMode });
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ describe("copyBundledResourceTrees", () => {
|
|||
recursive: true,
|
||||
});
|
||||
await mkdir(join(workspaceRoot, "craft", "sample"), { recursive: true });
|
||||
await mkdir(join(workspaceRoot, "plugins", "_official", "sample"), {
|
||||
recursive: true,
|
||||
});
|
||||
await mkdir(join(workspaceRoot, "assets", "frames"), { recursive: true });
|
||||
await mkdir(join(workspaceRoot, "assets", "community-pets", "sample"), {
|
||||
recursive: true,
|
||||
|
|
@ -47,6 +50,11 @@ describe("copyBundledResourceTrees", () => {
|
|||
});
|
||||
await writeFile(promptTemplatePath, "{\"id\":\"sample\"}\n", "utf8");
|
||||
await writeFile(communityPetPath, "{\"name\":\"sample\"}\n", "utf8");
|
||||
await writeFile(
|
||||
join(workspaceRoot, "plugins", "_official", "sample", "open-design.json"),
|
||||
"{\"id\":\"sample\"}\n",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
await copyBundledResourceTrees({ workspaceRoot, resourceRoot });
|
||||
|
||||
|
|
@ -62,6 +70,12 @@ describe("copyBundledResourceTrees", () => {
|
|||
"utf8",
|
||||
),
|
||||
).resolves.toBe("{\"name\":\"sample\"}\n");
|
||||
await expect(
|
||||
readFile(
|
||||
join(resourceRoot, "plugins", "_official", "sample", "open-design.json"),
|
||||
"utf8",
|
||||
),
|
||||
).resolves.toBe("{\"id\":\"sample\"}\n");
|
||||
} finally {
|
||||
await rm(root, { force: true, recursive: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { access, mkdir, mkdtemp, readFile, readlink, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { access, chmod, mkdir, mkdtemp, readFile, readlink, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import { tmpdir } from "node:os";
|
||||
import path, { join } from "node:path";
|
||||
|
|
@ -89,11 +89,13 @@ async function writeStandaloneFixture(
|
|||
async function runFixture(options: {
|
||||
includeHoistedNext?: boolean;
|
||||
includeWebNext: boolean;
|
||||
macAdhocBundleSign?: boolean;
|
||||
omitMacAdhocBundleSign?: boolean;
|
||||
omitRootWebPackage?: boolean;
|
||||
platformName?: "darwin" | "win32";
|
||||
requireRootWebPackageAudit?: boolean;
|
||||
useAbsolutePnpmSymlinks?: boolean;
|
||||
writeMacCodeBundleFixture?: boolean;
|
||||
}): Promise<{
|
||||
appOutDir: string;
|
||||
auditReportPath: string;
|
||||
|
|
@ -112,11 +114,24 @@ async function runFixture(options: {
|
|||
const resourcesRoot = platformName === "darwin"
|
||||
? join(appOutDir, "Open Design.app", "Contents", "Resources")
|
||||
: join(appOutDir, "resources");
|
||||
const appPath = join(appOutDir, "Open Design.app");
|
||||
const auditReportPath = join(root, "audit.json");
|
||||
const configPath = join(root, "config.json");
|
||||
const oldConfigEnv = process.env[CONFIG_ENV];
|
||||
|
||||
await mkdir(resourcesRoot, { recursive: true });
|
||||
if (platformName === "darwin" && options.writeMacCodeBundleFixture) {
|
||||
const frameworksRoot = join(appPath, "Contents", "Frameworks");
|
||||
const electronFrameworkRoot = join(frameworksRoot, "Electron Framework.framework");
|
||||
await mkdir(join(electronFrameworkRoot, "Resources"), { recursive: true });
|
||||
await mkdir(join(electronFrameworkRoot, "Versions", "A", "Resources"), { recursive: true });
|
||||
await mkdir(join(electronFrameworkRoot, "Versions", "Current", "Resources"), { recursive: true });
|
||||
await writeFile(join(electronFrameworkRoot, "Electron Framework"), "binary\n", "utf8");
|
||||
await writeFile(join(electronFrameworkRoot, "Versions", "A", "Electron Framework"), "binary\n", "utf8");
|
||||
await writeFile(join(electronFrameworkRoot, "Versions", "Current", "Electron Framework"), "binary\n", "utf8");
|
||||
await mkdir(join(frameworksRoot, "ReactiveObjC.framework"), { recursive: true });
|
||||
await mkdir(join(frameworksRoot, "Open Design Helper.app"), { recursive: true });
|
||||
}
|
||||
if (options.omitRootWebPackage !== true) {
|
||||
await writeRootWebPackage(resourcesRoot);
|
||||
}
|
||||
|
|
@ -125,7 +140,7 @@ async function runFixture(options: {
|
|||
`${JSON.stringify(
|
||||
{
|
||||
auditReportPath,
|
||||
...(options.omitMacAdhocBundleSign ? {} : { macAdhocBundleSign: false }),
|
||||
...(options.omitMacAdhocBundleSign ? {} : { macAdhocBundleSign: options.macAdhocBundleSign ?? false }),
|
||||
pruneCopiedSharp: false,
|
||||
pruneRootNext: false,
|
||||
pruneRootSharp: false,
|
||||
|
|
@ -270,4 +285,98 @@ describe("web standalone afterPack hook", () => {
|
|||
await rm(fixture.root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
darwinSymlinkIt("signs versioned mac frameworks at their Current version path", async () => {
|
||||
const codesignRoot = await mkdtemp(join(tmpdir(), "open-design-fake-codesign-"));
|
||||
const codesignBin = join(codesignRoot, "bin");
|
||||
const codesignLog = join(codesignRoot, "codesign.log");
|
||||
const oldPath = process.env.PATH;
|
||||
const oldCodesignLog = process.env.OD_FAKE_CODESIGN_LOG;
|
||||
|
||||
await mkdir(codesignBin, { recursive: true });
|
||||
await writeFile(
|
||||
join(codesignBin, "codesign"),
|
||||
"#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$OD_FAKE_CODESIGN_LOG\"\n",
|
||||
"utf8",
|
||||
);
|
||||
await chmod(join(codesignBin, "codesign"), 0o755);
|
||||
|
||||
process.env.PATH = `${codesignBin}${path.delimiter}${oldPath ?? ""}`;
|
||||
process.env.OD_FAKE_CODESIGN_LOG = codesignLog;
|
||||
|
||||
let fixture: Awaited<ReturnType<typeof runFixture>> | null = null;
|
||||
try {
|
||||
fixture = await runFixture({
|
||||
includeWebNext: true,
|
||||
macAdhocBundleSign: true,
|
||||
platformName: "darwin",
|
||||
writeMacCodeBundleFixture: true,
|
||||
});
|
||||
|
||||
const report = JSON.parse(await readFile(fixture.auditReportPath, "utf8")) as {
|
||||
macAdhocBundleSign: string[];
|
||||
};
|
||||
const signedTargets = report.macAdhocBundleSign.map((target) => target.split(path.sep).join("/"));
|
||||
const codesignInvocations = await readFile(codesignLog, "utf8");
|
||||
|
||||
expect(signedTargets).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringMatching(/Electron Framework\.framework\/Versions\/Current$/),
|
||||
expect.stringMatching(/ReactiveObjC\.framework$/),
|
||||
expect.stringMatching(/Open Design Helper\.app$/),
|
||||
expect.stringMatching(/Open Design\.app$/),
|
||||
]),
|
||||
);
|
||||
expect(signedTargets).not.toContainEqual(expect.stringMatching(/Electron Framework\.framework$/));
|
||||
await expect(
|
||||
readlink(join(
|
||||
fixture.appOutDir,
|
||||
"Open Design.app",
|
||||
"Contents",
|
||||
"Frameworks",
|
||||
"Electron Framework.framework",
|
||||
"Versions",
|
||||
"Current",
|
||||
)),
|
||||
).resolves.toBe("A");
|
||||
await expect(
|
||||
readlink(join(
|
||||
fixture.appOutDir,
|
||||
"Open Design.app",
|
||||
"Contents",
|
||||
"Frameworks",
|
||||
"Electron Framework.framework",
|
||||
"Electron Framework",
|
||||
)),
|
||||
).resolves.toBe("Versions/Current/Electron Framework");
|
||||
await expect(
|
||||
readlink(join(
|
||||
fixture.appOutDir,
|
||||
"Open Design.app",
|
||||
"Contents",
|
||||
"Frameworks",
|
||||
"Electron Framework.framework",
|
||||
"Resources",
|
||||
)),
|
||||
).resolves.toBe("Versions/Current/Resources");
|
||||
expect(codesignInvocations.split(path.sep).join("/")).toContain(
|
||||
"Electron Framework.framework/Versions/Current",
|
||||
);
|
||||
} finally {
|
||||
if (fixture != null) {
|
||||
await rm(fixture.root, { force: true, recursive: true });
|
||||
}
|
||||
await rm(codesignRoot, { force: true, recursive: true });
|
||||
if (oldPath == null) {
|
||||
delete process.env.PATH;
|
||||
} else {
|
||||
process.env.PATH = oldPath;
|
||||
}
|
||||
if (oldCodesignLog == null) {
|
||||
delete process.env.OD_FAKE_CODESIGN_LOG;
|
||||
} else {
|
||||
process.env.OD_FAKE_CODESIGN_LOG = oldCodesignLog;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ const PACKAGE_DIRS = [
|
|||
"packages/sidecar-proto",
|
||||
"packages/sidecar",
|
||||
"packages/platform",
|
||||
"packages/agui-adapter",
|
||||
"packages/plugin-runtime",
|
||||
"apps/daemon",
|
||||
"apps/web",
|
||||
"apps/desktop",
|
||||
|
|
|
|||
Loading…
Reference in a new issue