History

Fix executor beta packaging for 1Password wasm

Rhys Sullivan committed 7 months ago

Commit af3f4f91 parent 128b7776 files · +97 −2Browse files

Unified · Split

6 files
  1. .changeset/fuzzy-camels-cheer.md +5 −0
  2. .changeset/pre.json +1 −0
  3. apps/executor/CHANGELOG.md +6 −0
  4. apps/executor/package.json +1 −1
  5. apps/executor/src/distribution/artifact.ts +75 −1
  6. apps/executor/src/distribution/distribution.test.ts +9 −0
.changeset/fuzzy-camels-cheer.md+5−0
@@ -0,0 +1,5 @@
1---
2"executor": patch
3---
4
5Fix the packaged beta so the bundled 1Password SDK can load its wasm asset after install.
.changeset/pre.json+1−0
@@ -51,6 +51,7 @@ · 50 unmodified lines
51 },51 },
52 "changesets": [52 "changesets": [
53 "funny-forks-listen",53 "funny-forks-listen",
54 "fuzzy-camels-cheer",
54 "tame-trains-confess"55 "tame-trains-confess"
55 ]56 ]
56}57}
apps/executor/CHANGELOG.md+6−0
@@ -1,5 +1,11 @@
1# executor1# executor
2 2
3## 1.3.0-beta.2
4
5### Patch Changes
6
7- Fix the packaged beta so the bundled 1Password SDK can load its wasm asset after install.
8
3## 1.3.0-beta.19## 1.3.0-beta.1
4 10
5### Patch Changes11### Patch Changes
apps/executor/package.json+1−1
@@ -1,6 +1,6 @@
1{1{
2 "name": "executor",2 "name": "executor",
3 "version": "1.3.0-beta.1",3 "version": "1.3.0-beta.2",
4 "description": "Local AI executor with a CLI, local API server, and web UI.",4 "description": "Local AI executor with a CLI, local API server, and web UI.",
5 "keywords": [5 "keywords": [
6 "executor",6 "executor",
apps/executor/src/distribution/artifact.ts+75−1
@@ -1,5 +1,5 @@
1import { spawn } from "node:child_process";1import { spawn } from "node:child_process";
2import { existsSync } from "node:fs";2import { existsSync, readFileSync } from "node:fs";
3import { chmod, cp, mkdir, rename, rm, writeFile } from "node:fs/promises";3import { chmod, cp, mkdir, rename, rm, writeFile } from "node:fs/promises";
4import { createRequire } from "node:module";4import { createRequire } from "node:module";
5import { dirname, join, resolve } from "node:path";5import { dirname, join, resolve } from "node:path";
@@ -23,6 +23,12 @@ export type DistributionPackageArtifact = { · 17 unmodified lines
23 resourcesDir: string;23 resourcesDir: string;
24};24};
25 25
26type BundledDependency = {
27 name: string;
28 version: string;
29 packageDir: string;
30};
31
26type CommandInput = {32type CommandInput = {
27 command: string;33 command: string;
28 args: ReadonlyArray<string>;34 args: ReadonlyArray<string>;
@@ -90,6 +96,56 @@ const resolveQuickJsWasmPath = (): string => { · 61 unmodified lines
90 return wasmPath;96 return wasmPath;
91};97};
92 98
99const resolveInstalledPackage = (input: {
100 packageName: string;
101 fromPackageJsonPath: string;
102}): BundledDependency => {
103 const requireFromPackage = createRequire(input.fromPackageJsonPath);
104 const packageEntrypointPath = requireFromPackage.resolve(input.packageName);
105 let packageDir = dirname(packageEntrypointPath);
106 let packageJsonPath = join(packageDir, "package.json");
107
108 while (!existsSync(packageJsonPath)) {
109 const parentDir = dirname(packageDir);
110 if (parentDir === packageDir) {
111 throw new Error(`Unable to locate package.json for ${input.packageName} from ${packageEntrypointPath}`);
112 }
113 packageDir = parentDir;
114 packageJsonPath = join(packageDir, "package.json");
115 }
116
117 const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
118 name?: string;
119 version?: string;
120 };
121
122 if (!packageJson.name || !packageJson.version) {
123 throw new Error(`Invalid package metadata at ${packageJsonPath}`);
124 }
125
126 return {
127 name: packageJson.name,
128 version: packageJson.version,
129 packageDir,
130 };
131};
132
133const resolveBundledDependencies = (): ReadonlyArray<BundledDependency> => {
134 const onePasswordSdk = resolveInstalledPackage({
135 packageName: "@1password/sdk",
136 fromPackageJsonPath: join(repoRoot, "plugins/onepassword/sdk/package.json"),
137 });
138 const onePasswordSdkCore = resolveInstalledPackage({
139 packageName: "@1password/sdk-core",
140 fromPackageJsonPath: join(onePasswordSdk.packageDir, "package.json"),
141 });
142
143 return [
144 onePasswordSdk,
145 onePasswordSdkCore,
146 ];
147};
148
93 149
94const createPackageJson = (input: {150const createPackageJson = (input: {
95 packageName: string;151 packageName: string;
@@ -105,6 +161,8 @@ const createPackageJson = (input: { · 9 unmodified lines
105 url?: string;161 url?: string;
106 };162 };
107 license?: string;163 license?: string;
164 dependencies?: Readonly<Record<string, string>>;
165 bundleDependencies?: ReadonlyArray<string>;
108}) => {166}) => {
109 const packageJson = {167 const packageJson = {
110 name: input.packageName,168 name: input.packageName,
@@ -122,6 +180,7 @@ const createPackageJson = (input: { · 11 unmodified lines
122 },180 },
123 files: [181 files: [
124 "bin",182 "bin",
183 "node_modules",
125 "resources",184 "resources",
126 "README.md",185 "README.md",
127 "package.json",186 "package.json",
@@ -129,6 +188,8 @@ const createPackageJson = (input: { · 1 unmodified line
129 engines: {188 engines: {
130 node: ">=20",189 node: ">=20",
131 },190 },
191 dependencies: input.dependencies,
192 bundleDependencies: input.bundleDependencies,
132 };193 };
133 194
134 return JSON.stringify(packageJson, null, 2) + "\n";195 return JSON.stringify(packageJson, null, 2) + "\n";
@@ -195,6 +256,7 @@ const buildCliBundle = async (input: { · 60 unmodified lines
195 "node",256 "node",
196 "--outdir",257 "--outdir",
197 input.binDir,258 input.binDir,
259 "--external=@1password/sdk",
198 ],260 ],
199 cwd: repoRoot,261 cwd: repoRoot,
200 });262 });
@@ -217,7 +279,9 @@ export const buildDistributionPackage = async ( · 16 unmodified lines
217 const webDir = join(resourcesDir, "web");279 const webDir = join(resourcesDir, "web");
218 const bundlePath = join(binDir, "executor.mjs");280 const bundlePath = join(binDir, "executor.mjs");
219 const launcherPath = join(binDir, "executor.js");281 const launcherPath = join(binDir, "executor.js");
282 const nodeModulesDir = join(packageDir, "node_modules");
220 const quickJsWasmPath = resolveQuickJsWasmPath();283 const quickJsWasmPath = resolveQuickJsWasmPath();
284 const bundledDependencies = resolveBundledDependencies();
221 285
222 const webDistDir = join(repoRoot, "apps/web/dist");286 const webDistDir = join(repoRoot, "apps/web/dist");
223 const readmePath = join(repoRoot, "README.md");287 const readmePath = join(repoRoot, "README.md");
@@ -225,6 +289,7 @@ export const buildDistributionPackage = async ( · 1 unmodified line
225 const packageVersion = options.packageVersion ?? defaults.version;289 const packageVersion = options.packageVersion ?? defaults.version;
226 await rm(packageDir, { recursive: true, force: true });290 await rm(packageDir, { recursive: true, force: true });
227 await mkdir(binDir, { recursive: true });291 await mkdir(binDir, { recursive: true });
292 await mkdir(nodeModulesDir, { recursive: true });
228 await mkdir(resourcesDir, { recursive: true });293 await mkdir(resourcesDir, { recursive: true });
229 294
230 if ((options.buildWeb ?? true) || !existsSync(webDistDir)) {295 if ((options.buildWeb ?? true) || !existsSync(webDistDir)) {
@@ -245,6 +310,11 @@ export const buildDistributionPackage = async ( · 14 unmodified lines
245 });310 });
246 311
247 await cp(webDistDir, webDir, { recursive: true });312 await cp(webDistDir, webDir, { recursive: true });
313 for (const dependency of bundledDependencies) {
314 const destinationDir = join(nodeModulesDir, ...dependency.name.split("/"));
315 await mkdir(dirname(destinationDir), { recursive: true });
316 await cp(dependency.packageDir, destinationDir, { recursive: true });
317 }
248 await cp(quickJsWasmPath, join(binDir, "emscripten-module.wasm"));318 await cp(quickJsWasmPath, join(binDir, "emscripten-module.wasm"));
249 await cp(319 await cp(
250 join(repoRoot, "packages/kernel/runtime-deno-subprocess/src/deno-subprocess-worker.mjs"),320 join(repoRoot, "packages/kernel/runtime-deno-subprocess/src/deno-subprocess-worker.mjs"),
@@ -271,6 +341,10 @@ export const buildDistributionPackage = async ( · 20 unmodified lines
271 bugs: defaults.bugs,341 bugs: defaults.bugs,
272 repository: defaults.repository,342 repository: defaults.repository,
273 license: defaults.license,343 license: defaults.license,
344 dependencies: Object.fromEntries(
345 bundledDependencies.map((dependency) => [dependency.name, dependency.version]),
346 ),
347 bundleDependencies: bundledDependencies.map((dependency) => dependency.name),
274 }));348 }));
275 await cp(readmePath, join(packageDir, "README.md"));349 await cp(readmePath, join(packageDir, "README.md"));
276 await writeFile(launcherPath, createLauncherSource());350 await writeFile(launcherPath, createLauncherSource());
apps/executor/src/distribution/distribution.test.ts+9−0
@@ -1,3 +1,6 @@
1import { existsSync, readFileSync } from "node:fs";
2import { join } from "node:path";
3
1import { describe, expect, it } from "@effect/vitest";4import { describe, expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
3 6
@@ -17,6 +20,12 @@ describe("distribution flow", () => { · 13 unmodified lines
17 ) =>20 ) =>
18 Effect.gen(function* () {21 Effect.gen(function* () {
19 const harness = yield* DistributionHarness;22 const harness = yield* DistributionHarness;
23 const bundleSource = readFileSync(join(harness.packageDir, "bin/executor.mjs"), "utf8");
24
25 expect(bundleSource).not.toContain("core_bg.wasm");
26 expect(
27 existsSync(join(harness.packageDir, "node_modules/@1password/sdk-core/nodejs/core_bg.wasm")),
28 ).toBe(true);
20 29
21 yield* harness.writeProjectConfig(`{30 yield* harness.writeProjectConfig(`{
22 "runtime": "ses",31 "runtime": "ses",