@chee/patchwork-bundles 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +40 -0
  2. package/dist/esbuild.d.ts +2 -1
  3. package/dist/esbuild.d.ts.map +1 -0
  4. package/dist/esbuild.js +1 -0
  5. package/dist/esbuild.js.map +1 -0
  6. package/dist/farm.d.ts +1 -0
  7. package/dist/farm.d.ts.map +1 -0
  8. package/dist/farm.js +1 -0
  9. package/dist/farm.js.map +1 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +46 -2
  13. package/dist/index.js.map +1 -0
  14. package/dist/rollup.d.ts +1 -0
  15. package/dist/rollup.d.ts.map +1 -0
  16. package/dist/rollup.js +1 -0
  17. package/dist/rollup.js.map +1 -0
  18. package/dist/rspack.d.ts +1 -0
  19. package/dist/rspack.d.ts.map +1 -0
  20. package/dist/rspack.js +1 -0
  21. package/dist/rspack.js.map +1 -0
  22. package/dist/test/integration.test.d.ts +2 -0
  23. package/dist/test/integration.test.d.ts.map +1 -0
  24. package/dist/test/integration.test.js +172 -0
  25. package/dist/test/integration.test.js.map +1 -0
  26. package/dist/types.d.ts +16 -4
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +1 -0
  29. package/dist/types.js.map +1 -0
  30. package/dist/utils.d.ts +13 -0
  31. package/dist/utils.d.ts.map +1 -0
  32. package/dist/utils.js +70 -2
  33. package/dist/utils.js.map +1 -0
  34. package/dist/vite.d.ts +1 -0
  35. package/dist/vite.d.ts.map +1 -0
  36. package/dist/vite.js +1 -0
  37. package/dist/vite.js.map +1 -0
  38. package/dist/webpack.d.ts +1 -0
  39. package/dist/webpack.d.ts.map +1 -0
  40. package/dist/webpack.js +1 -0
  41. package/dist/webpack.js.map +1 -0
  42. package/package.json +10 -5
  43. package/.claude/settings.local.json +0 -17
  44. package/src/esbuild.ts +0 -2
  45. package/src/farm.ts +0 -2
  46. package/src/index.ts +0 -76
  47. package/src/rollup.ts +0 -2
  48. package/src/rspack.ts +0 -2
  49. package/src/types.ts +0 -41
  50. package/src/utils.ts +0 -96
  51. package/src/vite.ts +0 -2
  52. package/src/webpack.ts +0 -2
  53. package/test/integration.test.ts +0 -243
  54. package/tsconfig.json +0 -13
  55. package/vitest.config.ts +0 -10
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @chee/patchwork-bundles
2
+
3
+ ```sh
4
+ npm install @chee/patchwork-bundles
5
+ ```
6
+
7
+ ```ts
8
+ // vite.config.ts
9
+ import patchworkBundles from "@chee/patchwork-bundles/vite"
10
+
11
+ export default {
12
+ plugins: [patchworkBundles()],
13
+ }
14
+ ```
15
+
16
+ ```ts
17
+ // esbuild
18
+ import patchworkBundles from "@chee/patchwork-bundles/esbuild"
19
+
20
+ await esbuild.build({
21
+ plugins: [patchworkBundles()],
22
+ })
23
+ ```
24
+
25
+ ```ts
26
+ import patchworkBundles from "@chee/patchwork-bundles/rollup"
27
+ import patchworkBundles from "@chee/patchwork-bundles/webpack"
28
+ import patchworkBundles from "@chee/patchwork-bundles/rspack"
29
+ import patchworkBundles from "@chee/patchwork-bundles/farm"
30
+ ```
31
+
32
+ ```ts
33
+ patchworkBundles({
34
+ packageJsonPath: "./package.json",
35
+ rewrite: {
36
+ automerge: "patchwork", // | "bundle"
37
+ npm: "bundle", // | "esm.sh"
38
+ },
39
+ })
40
+ ```
package/dist/esbuild.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => import("unplugin").EsbuildPlugin;
1
+ declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => import("esbuild").Plugin;
2
2
  export default _default;
3
+ //# sourceMappingURL=esbuild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild.d.ts","sourceRoot":"","sources":["../src/esbuild.ts"],"names":[],"mappings":";AACA,wBAAwC"}
package/dist/esbuild.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { patchworkBundles } from "./index.js";
2
2
  export default patchworkBundles.esbuild;
3
+ //# sourceMappingURL=esbuild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild.js","sourceRoot":"","sources":["../src/esbuild.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,eAAe,gBAAgB,CAAC,OAAO,CAAC"}
package/dist/farm.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => JsPlugin;
2
2
  export default _default;
3
+ //# sourceMappingURL=farm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"farm.d.ts","sourceRoot":"","sources":["../src/farm.ts"],"names":[],"mappings":";AACA,wBAAqC"}
package/dist/farm.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { patchworkBundles } from "./index.js";
2
2
  export default patchworkBundles.farm;
3
+ //# sourceMappingURL=farm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"farm.js","sourceRoot":"","sources":["../src/farm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,eAAe,gBAAgB,CAAC,IAAI,CAAC"}
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ import { type PatchworkBundleOptions } from "./types.js";
2
2
  export declare const patchworkBundles: import("unplugin").UnpluginInstance<PatchworkBundleOptions | undefined, boolean>;
3
3
  export default patchworkBundles;
4
4
  export type { PatchworkBundleOptions } from "./types.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAUzE,eAAO,MAAM,gBAAgB,kFA0G5B,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAChC,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,21 +1,33 @@
1
1
  import { createUnplugin } from "unplugin";
2
2
  import { resolve } from "node:path";
3
- import { readPackageJson, parseBareSpecifier, isBareSpecifier, resolveDepEntryPoint, } from "./utils.js";
3
+ import { readPackageJson, parseBareSpecifier, isBareSpecifier, resolveDepEntryPoint, collectModuleExports, ensureLexer, } from "./utils.js";
4
4
  import { getImportableUrlFromAutomergeUrl } from "@inkandswitch/patchwork-filesystem";
5
5
  import externals from "@inkandswitch/patchwork-bootloader/externals";
6
6
  import { resolveOptions } from "./types.js";
7
+ // Prefix marking a virtual module we generate for
8
+ // `rewrite.automerge: "patchwork:cross-origin"`. The rest of the id is the
9
+ // original bare specifier. The leading NUL keeps other tooling from treating it
10
+ // as a real file path.
11
+ const CROSS_ORIGIN_PREFIX = "\0patchwork-bundle-cross-origin:";
12
+ const VALID_IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
7
13
  export const patchworkBundles = createUnplugin((rawOptions) => {
8
14
  const options = resolveOptions(rawOptions);
9
15
  let deps;
10
16
  return {
11
17
  name: "patchwork-bundles",
12
18
  enforce: "pre",
13
- buildStart() {
19
+ async buildStart() {
14
20
  const pkgPath = resolve(process.cwd(), options.packageJsonPath);
15
21
  const pkgJson = readPackageJson(pkgPath);
16
22
  deps = pkgJson.dependencies ?? {};
23
+ if (options.rewrite.automerge === "patchwork:cross-origin") {
24
+ await ensureLexer();
25
+ }
17
26
  },
18
27
  resolveId(id) {
28
+ // Virtual modules we emitted below are handled in load().
29
+ if (id.startsWith(CROSS_ORIGIN_PREFIX))
30
+ return id;
19
31
  if (!isBareSpecifier(id))
20
32
  return undefined;
21
33
  // Bootloader externals — always external (served via importmap)
@@ -36,6 +48,13 @@ export const patchworkBundles = createUnplugin((rawOptions) => {
36
48
  const url = getImportableUrlFromAutomergeUrl(version, entryPoint);
37
49
  return { id: url, external: true };
38
50
  }
51
+ // Cross-origin mode: defer URL construction to the browser so the
52
+ // service-worker path resolves against the *running document's*
53
+ // origin, not the (possibly cross-origin) module's. Emit a virtual
54
+ // re-export module; the code is generated in load().
55
+ if (options.rewrite.automerge === "patchwork:cross-origin") {
56
+ return CROSS_ORIGIN_PREFIX + id;
57
+ }
39
58
  return undefined;
40
59
  }
41
60
  // npm fallback — optionally rewrite to esm.sh
@@ -49,6 +68,31 @@ export const patchworkBundles = createUnplugin((rawOptions) => {
49
68
  }
50
69
  return undefined;
51
70
  },
71
+ load(id) {
72
+ if (!id.startsWith(CROSS_ORIGIN_PREFIX))
73
+ return undefined;
74
+ const spec = id.slice(CROSS_ORIGIN_PREFIX.length);
75
+ const { pkgName, subpath } = parseBareSpecifier(spec);
76
+ const version = deps[pkgName];
77
+ const entryPoint = resolveDepEntryPoint(pkgName, subpath);
78
+ // Enumerate the dep's named exports so the virtual module can re-export
79
+ // the same shape. Read the installed copy in node_modules — assumed to
80
+ // match the automerge doc the service worker serves.
81
+ const entryFile = resolve(process.cwd(), "node_modules", pkgName, entryPoint);
82
+ const exportNames = collectModuleExports(entryFile);
83
+ const hasDefault = exportNames.delete("default");
84
+ const named = [...exportNames].filter((n) => VALID_IDENT.test(n));
85
+ const lines = [
86
+ `import { getImportableUrlFromAutomergeUrl as __pwResolve } from "@inkandswitch/patchwork-filesystem";`,
87
+ `const __pwUrl = __pwResolve(${JSON.stringify(version)}, ${JSON.stringify(entryPoint)});`,
88
+ `const __pwMod = await import(/* @vite-ignore */ __pwUrl);`,
89
+ ...named.map((n) => `export const ${n} = __pwMod[${JSON.stringify(n)}];`),
90
+ ];
91
+ if (hasDefault)
92
+ lines.push(`export default __pwMod.default;`);
93
+ return { code: lines.join("\n"), moduleSideEffects: true };
94
+ },
52
95
  };
53
96
  });
54
97
  export default patchworkBundles;
98
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/rollup.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => import("rollup").Plugin<any> | import("rollup").Plugin<any>[];
2
2
  export default _default;
3
+ //# sourceMappingURL=rollup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollup.d.ts","sourceRoot":"","sources":["../src/rollup.ts"],"names":[],"mappings":";AACA,wBAAuC"}
package/dist/rollup.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { patchworkBundles } from "./index.js";
2
2
  export default patchworkBundles.rollup;
3
+ //# sourceMappingURL=rollup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollup.js","sourceRoot":"","sources":["../src/rollup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,eAAe,gBAAgB,CAAC,MAAM,CAAC"}
package/dist/rspack.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => RspackPluginInstance;
2
2
  export default _default;
3
+ //# sourceMappingURL=rspack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rspack.d.ts","sourceRoot":"","sources":["../src/rspack.ts"],"names":[],"mappings":";AACA,wBAAuC"}
package/dist/rspack.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { patchworkBundles } from "./index.js";
2
2
  export default patchworkBundles.rspack;
3
+ //# sourceMappingURL=rspack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rspack.js","sourceRoot":"","sources":["../src/rspack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,eAAe,gBAAgB,CAAC,MAAM,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../../src/test/integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,172 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
+ import * as fs from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { execSync } from "node:child_process";
6
+ import { build as viteBuild } from "vite";
7
+ import * as esbuild from "esbuild";
8
+ const AUTOMERGE_URL = "automerge:kFcrzeDmr5zXE1jShvxUPsoToAN";
9
+ const ENCODED_URL = encodeURIComponent(AUTOMERGE_URL);
10
+ describe("patchwork-bundles integration", () => {
11
+ let tmpDir;
12
+ let originalCwd;
13
+ beforeAll(async () => {
14
+ originalCwd = process.cwd();
15
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "patchwork-bundles-test-"));
16
+ // Phase 1: set up project with pnpm-plugin-patchwork as a configDependency
17
+ await fs.writeFile(path.join(tmpDir, "package.json"), JSON.stringify({
18
+ name: "test-patchwork-bundles",
19
+ version: "0.0.0",
20
+ type: "module",
21
+ dependencies: {
22
+ "my-sideboard-tool": AUTOMERGE_URL,
23
+ "solid-js": "^1.9.9",
24
+ },
25
+ }, null, 2));
26
+ // stub workspace packages that the automerge doc depends on via workspace:^
27
+ const bootloaderDir = path.join(tmpDir, "packages/patchwork-bootloader");
28
+ await fs.mkdir(bootloaderDir, { recursive: true });
29
+ await fs.writeFile(path.join(bootloaderDir, "package.json"), JSON.stringify({
30
+ name: "@inkandswitch/patchwork-bootloader",
31
+ version: "0.0.1",
32
+ type: "module",
33
+ exports: { "./externals": { import: "./externals.js" } },
34
+ }));
35
+ await fs.writeFile(path.join(bootloaderDir, "externals.js"), `const externals = [\n "solid-js",\n "solid-js/web",\n "solid-js/html",\n "solid-js/store",\n "solid-js/jsx-runtime",\n "solid-js/h"\n];\nexport default externals;\n`);
36
+ const elementsDir = path.join(tmpDir, "packages/patchwork-elements");
37
+ await fs.mkdir(elementsDir, { recursive: true });
38
+ await fs.writeFile(path.join(elementsDir, "package.json"), JSON.stringify({
39
+ name: "@inkandswitch/patchwork-elements",
40
+ version: "0.0.1",
41
+ type: "module",
42
+ }));
43
+ await fs.writeFile(path.join(tmpDir, "pnpm-workspace.yaml"), [
44
+ "packages:",
45
+ ' - "packages/*"',
46
+ "configDependencies:",
47
+ ' pnpm-plugin-patchwork: "0.1.0"',
48
+ "",
49
+ ].join("\n"));
50
+ // .npmrc — avoid strict peer deps issues
51
+ await fs.writeFile(path.join(tmpDir, ".npmrc"), "strict-peer-dependencies=false\nauto-install-peers=true\n");
52
+ // install — plugin resolves automerge: deps automatically
53
+ // filter out parent pnpm's npm_config_* env vars to avoid contamination
54
+ const cleanEnv = Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith("npm_")));
55
+ execSync("npx pnpm@next-11 install --no-frozen-lockfile", {
56
+ cwd: tmpDir,
57
+ stdio: "pipe",
58
+ timeout: 120_000,
59
+ env: {
60
+ ...cleanEnv,
61
+ PATCHWORK_SYNC_SERVER: "wss://sync3.automerge.org",
62
+ },
63
+ });
64
+ // Phase 2: write source file
65
+ const srcDir = path.join(tmpDir, "src");
66
+ await fs.mkdir(srcDir, { recursive: true });
67
+ await fs.writeFile(path.join(srcDir, "index.ts"), [
68
+ `import { createSignal } from "solid-js";`,
69
+ `import * as sideboard from "my-sideboard-tool";`,
70
+ `console.log(createSignal, sideboard);`,
71
+ "",
72
+ ].join("\n"));
73
+ process.chdir(tmpDir);
74
+ }, 180_000);
75
+ afterAll(async () => {
76
+ process.chdir(originalCwd);
77
+ await fs.rm(tmpDir, { recursive: true, force: true });
78
+ });
79
+ describe("pnpm plugin installed my-sideboard-tool from automerge", () => {
80
+ it("has a package.json with the sideboard package name", async () => {
81
+ const pkgPath = path.join(tmpDir, "node_modules/my-sideboard-tool/package.json");
82
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
83
+ expect(pkg.name).toBe("@chee/patchwork-sideboard");
84
+ });
85
+ it("has an exports field with a patchwork condition", async () => {
86
+ const pkgPath = path.join(tmpDir, "node_modules/my-sideboard-tool/package.json");
87
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
88
+ expect(pkg.exports).toBeDefined();
89
+ expect(pkg.exports["."]).toBeDefined();
90
+ expect(pkg.exports["."].patchwork).toBeDefined();
91
+ });
92
+ it("contains actual dist files from the automerge document", async () => {
93
+ const distDir = path.join(tmpDir, "node_modules/my-sideboard-tool/dist");
94
+ const files = await fs.readdir(distDir);
95
+ expect(files.length).toBeGreaterThan(0);
96
+ // should have js output
97
+ expect(files.some((f) => f.endsWith(".js"))).toBe(true);
98
+ });
99
+ });
100
+ describe("vite build", () => {
101
+ let output;
102
+ beforeAll(async () => {
103
+ // Determine what entry point the plugin will resolve to
104
+ const pluginPath = path.resolve(originalCwd, "dist/vite.js");
105
+ const { default: vitePlugin } = await import(pluginPath);
106
+ const result = await viteBuild({
107
+ root: tmpDir,
108
+ logLevel: "silent",
109
+ build: {
110
+ lib: {
111
+ entry: path.join(tmpDir, "src/index.ts"),
112
+ formats: ["es"],
113
+ fileName: "index",
114
+ },
115
+ outDir: path.join(tmpDir, "dist-vite"),
116
+ write: true,
117
+ minify: false,
118
+ },
119
+ plugins: [vitePlugin()],
120
+ });
121
+ // vite 7 outputs .js, older versions output .mjs
122
+ const mjs = path.join(tmpDir, "dist-vite/index.mjs");
123
+ const js = path.join(tmpDir, "dist-vite/index.js");
124
+ output = await fs.readFile(await fs.access(mjs).then(() => mjs, () => js), "utf-8");
125
+ }, 60_000);
126
+ it("rewrites automerge dep to service-worker URL", () => {
127
+ expect(output).toContain(ENCODED_URL);
128
+ // Should be an import from /<encoded-url>/...
129
+ expect(output).toMatch(new RegExp(`from ["']/${ENCODED_URL}/`));
130
+ });
131
+ it("externalizes solid-js", () => {
132
+ expect(output).toMatch(/from ["']solid-js["']/);
133
+ // if solid-js were inlined, the output would be thousands of lines
134
+ const lines = output.split("\n").length;
135
+ expect(lines).toBeLessThan(20);
136
+ });
137
+ it("does not contain bundled automerge dep code", () => {
138
+ // The automerge dep should be external, not inlined
139
+ expect(output).not.toContain("my-sideboard-tool");
140
+ });
141
+ });
142
+ describe("esbuild build", () => {
143
+ let output;
144
+ beforeAll(async () => {
145
+ const pluginPath = path.resolve(originalCwd, "dist/esbuild.js");
146
+ const { default: esbuildPlugin } = await import(pluginPath);
147
+ await esbuild.build({
148
+ entryPoints: [path.join(tmpDir, "src/index.ts")],
149
+ bundle: true,
150
+ format: "esm",
151
+ outfile: path.join(tmpDir, "dist-esbuild/index.js"),
152
+ plugins: [esbuildPlugin()],
153
+ logLevel: "silent",
154
+ });
155
+ output = await fs.readFile(path.join(tmpDir, "dist-esbuild/index.js"), "utf-8");
156
+ }, 60_000);
157
+ it("rewrites automerge dep to service-worker URL", () => {
158
+ expect(output).toContain(ENCODED_URL);
159
+ expect(output).toMatch(new RegExp(`from ["']/${ENCODED_URL}/`));
160
+ });
161
+ it("externalizes solid-js", () => {
162
+ expect(output).toMatch(/from ["']solid-js["']/);
163
+ // if solid-js were inlined, the output would be thousands of lines
164
+ const lines = output.split("\n").length;
165
+ expect(lines).toBeLessThan(20);
166
+ });
167
+ it("does not contain bundled automerge dep code", () => {
168
+ expect(output).not.toContain("my-sideboard-tool");
169
+ });
170
+ });
171
+ });
172
+ //# sourceMappingURL=integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.js","sourceRoot":"","sources":["../../src/test/integration.test.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -4,12 +4,23 @@ export interface PatchworkBundleOptions {
4
4
  rewrite?: {
5
5
  /**
6
6
  * How to handle dependencies with `automerge:` version specifiers.
7
- * - `"patchwork"` (default): rewrite bare imports to service-worker URLs
8
- * (`/{encodeURIComponent("automerge:docid")}/resolved/entry.js`),
7
+ * - `"patchwork"` (default): rewrite bare imports to *root-relative*
8
+ * service-worker URLs (`/{encodeURIComponent("automerge:docid")}/resolved/entry.js`),
9
9
  * resolving through the dep's package.json exports with the `"patchwork"` condition.
10
+ * The leading `/` resolves against the *importing module's* origin, so this
11
+ * only works when the tool is served same-origin as the patchwork service
12
+ * worker. A tool served from a different origin (e.g. a separate static
13
+ * host) will 404 on these imports.
14
+ * - `"patchwork:cross-origin"`: rewrite bare imports to a virtual module
15
+ * that resolves the URL *at runtime* via `getImportableUrlFromAutomergeUrl`
16
+ * and dynamically imports it, re-exporting the dep's named exports
17
+ * (top-level await). This resolves against the running document's origin
18
+ * instead of the module's, so it works even when the tool is loaded
19
+ * cross-origin from where the service worker lives. Requires a build
20
+ * target that supports top-level await (e.g. `es2022`/`esnext`).
10
21
  * - `"bundle"`: include the dependency code in the bundle normally.
11
22
  */
12
- automerge?: "patchwork" | "bundle";
23
+ automerge?: "patchwork" | "patchwork:cross-origin" | "bundle";
13
24
  /**
14
25
  * How to handle npm dependencies (deps that aren't `automerge:` URLs
15
26
  * and aren't in the bootloader externals list).
@@ -22,8 +33,9 @@ export interface PatchworkBundleOptions {
22
33
  export interface ResolvedOptions {
23
34
  packageJsonPath: string;
24
35
  rewrite: {
25
- automerge: "patchwork" | "bundle";
36
+ automerge: "patchwork" | "patchwork:cross-origin" | "bundle";
26
37
  npm: "esm.sh" | "bundle";
27
38
  };
28
39
  }
29
40
  export declare function resolveOptions(raw?: PatchworkBundleOptions): ResolvedOptions;
41
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,EAAE;QACR;;;;;;;;;;;;;;;;;WAiBG;QACH,SAAS,CAAC,EAAE,WAAW,GAAG,wBAAwB,GAAG,QAAQ,CAAC;QAE9D;;;;;WAKG;QACH,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE;QACP,SAAS,EAAE,WAAW,GAAG,wBAAwB,GAAG,QAAQ,CAAC;QAC7D,GAAG,EAAE,QAAQ,GAAG,QAAQ,CAAC;KAC1B,CAAC;CACH;AAED,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,sBAAsB,GAAG,eAAe,CAQ5E"}
package/dist/types.js CHANGED
@@ -7,3 +7,4 @@ export function resolveOptions(raw) {
7
7
  },
8
8
  };
9
9
  }
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA2CA,MAAM,UAAU,cAAc,CAAC,GAA4B;IACzD,OAAO;QACL,eAAe,EAAE,GAAG,EAAE,eAAe,IAAI,gBAAgB;QACzD,OAAO,EAAE;YACP,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,IAAI,WAAW;YACjD,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,QAAQ;SACnC;KACF,CAAC;AACJ,CAAC"}
package/dist/utils.d.ts CHANGED
@@ -27,3 +27,16 @@ export declare function isBareSpecifier(id: string): boolean;
27
27
  * or the subpath itself if resolution fails.
28
28
  */
29
29
  export declare function resolveDepEntryPoint(pkgName: string, subpath?: string, conditions?: string[]): string;
30
+ /**
31
+ * Enumerate the *named* exports of an ES module file, following relative
32
+ * `export * from "./x"` re-exports one dependency at a time. Used to generate a
33
+ * runtime-resolved virtual module that re-exports the same names as the real
34
+ * module. `"default"` is included in the returned set if the module has a
35
+ * default export.
36
+ *
37
+ * es-module-lexer must be initialised first (see {@link ensureLexer}).
38
+ */
39
+ export declare function collectModuleExports(entryPath: string, seen?: Set<string>): Set<string>;
40
+ /** Initialise es-module-lexer's WASM once; safe to await repeatedly. */
41
+ export declare function ensureLexer(): Promise<void>;
42
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAuBA;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAY,EACrB,UAAU,GAAE,MAAM,EAAuC,GACxD,MAAM,CAwBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,GAAG,CAAC,MAAM,CAAa,GAC5B,GAAG,CAAC,MAAM,CAAC,CAiCb;AAmBD,wEAAwE;AACxE,wBAAgB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3C"}
package/dist/utils.js CHANGED
@@ -1,6 +1,7 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve as resolvePath } from "node:path";
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { resolve as resolvePath, dirname } from "node:path";
3
3
  import { resolve as resolveExports } from "resolve.exports";
4
+ import { init as initLexer, parse as parseModule } from "es-module-lexer";
4
5
  /**
5
6
  * Read and parse a package.json file.
6
7
  */
@@ -71,3 +72,70 @@ export function resolveDepEntryPoint(pkgName, subpath = ".", conditions = ["patc
71
72
  }
72
73
  return subpath;
73
74
  }
75
+ /**
76
+ * Enumerate the *named* exports of an ES module file, following relative
77
+ * `export * from "./x"` re-exports one dependency at a time. Used to generate a
78
+ * runtime-resolved virtual module that re-exports the same names as the real
79
+ * module. `"default"` is included in the returned set if the module has a
80
+ * default export.
81
+ *
82
+ * es-module-lexer must be initialised first (see {@link ensureLexer}).
83
+ */
84
+ export function collectModuleExports(entryPath, seen = new Set()) {
85
+ const names = new Set();
86
+ if (seen.has(entryPath) || !existsSync(entryPath))
87
+ return names;
88
+ seen.add(entryPath);
89
+ let source;
90
+ try {
91
+ source = readFileSync(entryPath, "utf-8");
92
+ }
93
+ catch {
94
+ return names;
95
+ }
96
+ const [imports, exports] = parseModule(source);
97
+ for (const e of exports) {
98
+ if (e.n)
99
+ names.add(e.n);
100
+ }
101
+ // `export * from "./rel"` contributes names that aren't listed inline. Those
102
+ // re-exports show up in `imports`; detect them by the statement text and
103
+ // recurse into relative targets.
104
+ for (const imp of imports) {
105
+ const spec = imp.n;
106
+ if (!spec || !spec.startsWith("."))
107
+ continue;
108
+ const stmt = source.slice(imp.ss, imp.se);
109
+ if (!/^export\s*\*/.test(stmt))
110
+ continue;
111
+ const target = resolveRelativeModule(dirname(entryPath), spec);
112
+ if (!target)
113
+ continue;
114
+ for (const n of collectModuleExports(target, seen)) {
115
+ if (n !== "default")
116
+ names.add(n); // `export *` never re-exports default
117
+ }
118
+ }
119
+ return names;
120
+ }
121
+ /**
122
+ * Resolve a relative module specifier to an existing file, trying the literal
123
+ * path and common JS extensions / index files.
124
+ */
125
+ function resolveRelativeModule(fromDir, spec) {
126
+ const base = resolvePath(fromDir, spec);
127
+ const candidates = [
128
+ base,
129
+ `${base}.js`,
130
+ `${base}.mjs`,
131
+ resolvePath(base, "index.js"),
132
+ resolvePath(base, "index.mjs"),
133
+ ];
134
+ return candidates.find((c) => existsSync(c)) ?? null;
135
+ }
136
+ let lexerReady;
137
+ /** Initialise es-module-lexer's WASM once; safe to await repeatedly. */
138
+ export function ensureLexer() {
139
+ return (lexerReady ??= initLexer);
140
+ }
141
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/dist/vite.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
2
2
  export default _default;
3
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":";AACA,wBAAqC"}
package/dist/vite.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { patchworkBundles } from "./index.js";
2
2
  export default patchworkBundles.vite;
3
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,eAAe,gBAAgB,CAAC,IAAI,CAAC"}
package/dist/webpack.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  declare const _default: (options?: import("./types.js").PatchworkBundleOptions | undefined) => WebpackPluginInstance;
2
2
  export default _default;
3
+ //# sourceMappingURL=webpack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack.d.ts","sourceRoot":"","sources":["../src/webpack.ts"],"names":[],"mappings":";AACA,wBAAwC"}
package/dist/webpack.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { patchworkBundles } from "./index.js";
2
2
  export default patchworkBundles.webpack;
3
+ //# sourceMappingURL=webpack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack.js","sourceRoot":"","sources":["../src/webpack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,eAAe,gBAAgB,CAAC,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@chee/patchwork-bundles",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "author": "Ink & Switch",
6
6
  "license": "MIT",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "exports": {
8
11
  ".": {
9
12
  "import": "./dist/index.js",
@@ -35,14 +38,16 @@
35
38
  }
36
39
  },
37
40
  "dependencies": {
38
- "@automerge/automerge-repo": "2.6.0-subduction.15",
41
+ "@automerge/automerge-repo": "2.6.0-subduction.40",
42
+ "@inkandswitch/patchwork-bootloader": "*",
43
+ "@inkandswitch/patchwork-filesystem": "*",
44
+ "es-module-lexer": "^2.3.0",
39
45
  "resolve.exports": "^2.0.3",
40
- "unplugin": "^2.3.0",
41
- "@inkandswitch/patchwork-bootloader": "^0.0.5",
42
- "@inkandswitch/patchwork-filesystem": "^0.0.4"
46
+ "unplugin": "^2.3.0"
43
47
  },
44
48
  "devDependencies": {
45
49
  "esbuild": "^0.23.1",
50
+ "typescript": "^6.0.3",
46
51
  "vite": "^7.1.9",
47
52
  "vitest": "^2.1.8"
48
53
  },
@@ -1,17 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "WebFetch(domain:www.npmjs.com)",
5
- "WebSearch",
6
- "Bash(find:*)",
7
- "WebFetch(domain:registry.npmjs.org)",
8
- "Bash(pnpm --version:*)",
9
- "Bash(npx pnpm@next-11:*)",
10
- "Bash(npm view:*)",
11
- "Bash(ls:*)",
12
- "Bash(echo:*)",
13
- "Bash(pnpm test:*)",
14
- "Bash(node -e:*)"
15
- ]
16
- }
17
- }
package/src/esbuild.ts DELETED
@@ -1,2 +0,0 @@
1
- import { patchworkBundles } from "./index.js";
2
- export default patchworkBundles.esbuild;
package/src/farm.ts DELETED
@@ -1,2 +0,0 @@
1
- import { patchworkBundles } from "./index.js";
2
- export default patchworkBundles.farm;
package/src/index.ts DELETED
@@ -1,76 +0,0 @@
1
- import { createUnplugin } from "unplugin";
2
- import { resolve } from "node:path";
3
- import {
4
- readPackageJson,
5
- parseBareSpecifier,
6
- isBareSpecifier,
7
- resolveDepEntryPoint,
8
- } from "./utils.js";
9
- import { getImportableUrlFromAutomergeUrl } from "@inkandswitch/patchwork-filesystem";
10
- import externals from "@inkandswitch/patchwork-bootloader/externals";
11
- import type { AutomergeUrl } from "@automerge/automerge-repo";
12
- import { resolveOptions, type PatchworkBundleOptions } from "./types.js";
13
-
14
- export const patchworkBundles = createUnplugin(
15
- (rawOptions?: PatchworkBundleOptions) => {
16
- const options = resolveOptions(rawOptions);
17
- let deps: Record<string, string>;
18
-
19
- return {
20
- name: "patchwork-bundles",
21
- enforce: "pre",
22
-
23
- buildStart() {
24
- const pkgPath = resolve(process.cwd(), options.packageJsonPath);
25
- const pkgJson = readPackageJson(pkgPath);
26
- deps = pkgJson.dependencies ?? {};
27
- },
28
-
29
- resolveId(id: string) {
30
- if (!isBareSpecifier(id)) return undefined;
31
-
32
- // Bootloader externals — always external (served via importmap)
33
- if (externals.includes(id)) {
34
- return { id, external: true };
35
- }
36
-
37
- const { pkgName, subpath } = parseBareSpecifier(id);
38
-
39
- if (pkgName !== id && externals.includes(pkgName)) {
40
- return { id, external: true };
41
- }
42
-
43
- const version = deps[pkgName];
44
- if (!version) return undefined;
45
-
46
- // Automerge deps — rewrite to service worker URLs
47
- if (version.startsWith("automerge:")) {
48
- if (options.rewrite.automerge === "patchwork") {
49
- const entryPoint = resolveDepEntryPoint(pkgName, subpath);
50
- const url = getImportableUrlFromAutomergeUrl(
51
- version as AutomergeUrl,
52
- entryPoint
53
- );
54
- return { id: url, external: true };
55
- }
56
- return undefined;
57
- }
58
-
59
- // npm fallback — optionally rewrite to esm.sh
60
- if (options.rewrite.npm === "esm.sh") {
61
- const cleanVersion = version.replace(/^[\^~>=<\s]+/, "");
62
- const esmId = subpath === "." ? pkgName : id;
63
- return {
64
- id: `https://esm.sh/${esmId}@${cleanVersion}`,
65
- external: true,
66
- };
67
- }
68
-
69
- return undefined;
70
- },
71
- };
72
- }
73
- );
74
-
75
- export default patchworkBundles;
76
- export type { PatchworkBundleOptions } from "./types.js";
package/src/rollup.ts DELETED
@@ -1,2 +0,0 @@
1
- import { patchworkBundles } from "./index.js";
2
- export default patchworkBundles.rollup;
package/src/rspack.ts DELETED
@@ -1,2 +0,0 @@
1
- import { patchworkBundles } from "./index.js";
2
- export default patchworkBundles.rspack;
package/src/types.ts DELETED
@@ -1,41 +0,0 @@
1
- export interface PatchworkBundleOptions {
2
- /** Path to the project's package.json. Default: "./package.json" relative to cwd */
3
- packageJsonPath?: string;
4
-
5
- rewrite?: {
6
- /**
7
- * How to handle dependencies with `automerge:` version specifiers.
8
- * - `"patchwork"` (default): rewrite bare imports to service-worker URLs
9
- * (`/{encodeURIComponent("automerge:docid")}/resolved/entry.js`),
10
- * resolving through the dep's package.json exports with the `"patchwork"` condition.
11
- * - `"bundle"`: include the dependency code in the bundle normally.
12
- */
13
- automerge?: "patchwork" | "bundle";
14
-
15
- /**
16
- * How to handle npm dependencies (deps that aren't `automerge:` URLs
17
- * and aren't in the bootloader externals list).
18
- * - `"bundle"` (default): bundle them normally.
19
- * - `"esm.sh"`: rewrite to `https://esm.sh/<pkg>@<version>` external URLs.
20
- */
21
- npm?: "esm.sh" | "bundle";
22
- };
23
- }
24
-
25
- export interface ResolvedOptions {
26
- packageJsonPath: string;
27
- rewrite: {
28
- automerge: "patchwork" | "bundle";
29
- npm: "esm.sh" | "bundle";
30
- };
31
- }
32
-
33
- export function resolveOptions(raw?: PatchworkBundleOptions): ResolvedOptions {
34
- return {
35
- packageJsonPath: raw?.packageJsonPath ?? "./package.json",
36
- rewrite: {
37
- automerge: raw?.rewrite?.automerge ?? "patchwork",
38
- npm: raw?.rewrite?.npm ?? "bundle",
39
- },
40
- };
41
- }
package/src/utils.ts DELETED
@@ -1,96 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve as resolvePath } from "node:path";
3
- import { resolve as resolveExports } from "resolve.exports";
4
-
5
- /**
6
- * Read and parse a package.json file.
7
- */
8
- export function readPackageJson(path: string): Record<string, any> {
9
- return JSON.parse(readFileSync(path, "utf-8"));
10
- }
11
-
12
- /**
13
- * Parse a bare import specifier into package name and subpath.
14
- *
15
- * - `"abc"` → `{ pkgName: "abc", subpath: "." }`
16
- * - `"abc/utils"` → `{ pkgName: "abc", subpath: "./utils" }`
17
- * - `"@scope/pkg"` → `{ pkgName: "@scope/pkg", subpath: "." }`
18
- * - `"@scope/pkg/utils"` → `{ pkgName: "@scope/pkg", subpath: "./utils" }`
19
- */
20
- export function parseBareSpecifier(id: string): {
21
- pkgName: string;
22
- subpath: string;
23
- } {
24
- const firstSlash = id.indexOf("/");
25
- const isScoped = id.startsWith("@");
26
-
27
- if (isScoped) {
28
- const secondSlash = id.indexOf("/", firstSlash + 1);
29
- if (secondSlash === -1) {
30
- return { pkgName: id, subpath: "." };
31
- }
32
- return {
33
- pkgName: id.slice(0, secondSlash),
34
- subpath: "./" + id.slice(secondSlash + 1),
35
- };
36
- }
37
-
38
- if (firstSlash === -1) {
39
- return { pkgName: id, subpath: "." };
40
- }
41
-
42
- return {
43
- pkgName: id.slice(0, firstSlash),
44
- subpath: "./" + id.slice(firstSlash + 1),
45
- };
46
- }
47
-
48
- /**
49
- * Returns true if the specifier is a bare import (not relative, absolute,
50
- * or a protocol URL).
51
- */
52
- export function isBareSpecifier(id: string): boolean {
53
- return (
54
- id.length > 0 &&
55
- !id.startsWith(".") &&
56
- !id.startsWith("/") &&
57
- !id.includes(":")
58
- );
59
- }
60
-
61
- /**
62
- * Resolve a dependency's entry point by reading its package.json from
63
- * `node_modules/` and resolving through its `exports` field.
64
- *
65
- * Returns the resolved path relative to the package root (e.g. `"./dist/index.js"`),
66
- * or the subpath itself if resolution fails.
67
- */
68
- export function resolveDepEntryPoint(
69
- pkgName: string,
70
- subpath: string = ".",
71
- conditions: string[] = ["patchwork", "browser", "import"]
72
- ): string {
73
- try {
74
- const depPkgJsonPath = resolvePath(
75
- process.cwd(),
76
- "node_modules",
77
- pkgName,
78
- "package.json"
79
- );
80
- const depPkgJson = readPackageJson(depPkgJsonPath);
81
-
82
- const resolved = resolveExports(depPkgJson, subpath, { conditions });
83
- if (resolved && resolved[0]) {
84
- return resolved[0];
85
- }
86
-
87
- // Fallback to "main" for root export
88
- if (subpath === "." && typeof depPkgJson.main === "string") {
89
- return depPkgJson.main;
90
- }
91
- } catch {
92
- // If we can't read the dep's package.json, just pass through the subpath
93
- }
94
-
95
- return subpath;
96
- }
package/src/vite.ts DELETED
@@ -1,2 +0,0 @@
1
- import { patchworkBundles } from "./index.js";
2
- export default patchworkBundles.vite;
package/src/webpack.ts DELETED
@@ -1,2 +0,0 @@
1
- import { patchworkBundles } from "./index.js";
2
- export default patchworkBundles.webpack;
@@ -1,243 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import * as fs from "node:fs/promises";
3
- import * as os from "node:os";
4
- import * as path from "node:path";
5
- import { execSync } from "node:child_process";
6
- import { build as viteBuild } from "vite";
7
- import * as esbuild from "esbuild";
8
-
9
- const AUTOMERGE_URL = "automerge:kFcrzeDmr5zXE1jShvxUPsoToAN";
10
- const ENCODED_URL = encodeURIComponent(AUTOMERGE_URL);
11
-
12
- describe("patchwork-bundles integration", () => {
13
- let tmpDir: string;
14
- let originalCwd: string;
15
-
16
- beforeAll(async () => {
17
- originalCwd = process.cwd();
18
- tmpDir = await fs.mkdtemp(
19
- path.join(os.tmpdir(), "patchwork-bundles-test-")
20
- );
21
-
22
- // Phase 1: set up project with pnpm-plugin-patchwork as a configDependency
23
- await fs.writeFile(
24
- path.join(tmpDir, "package.json"),
25
- JSON.stringify(
26
- {
27
- name: "test-patchwork-bundles",
28
- version: "0.0.0",
29
- type: "module",
30
- dependencies: {
31
- "my-sideboard-tool": AUTOMERGE_URL,
32
- "solid-js": "^1.9.9",
33
- },
34
- },
35
- null,
36
- 2
37
- )
38
- );
39
-
40
- // stub workspace packages that the automerge doc depends on via workspace:^
41
- const bootloaderDir = path.join(tmpDir, "packages/patchwork-bootloader");
42
- await fs.mkdir(bootloaderDir, { recursive: true });
43
- await fs.writeFile(
44
- path.join(bootloaderDir, "package.json"),
45
- JSON.stringify({
46
- name: "@inkandswitch/patchwork-bootloader",
47
- version: "0.0.1",
48
- type: "module",
49
- exports: { "./externals": { import: "./externals.js" } },
50
- })
51
- );
52
- await fs.writeFile(
53
- path.join(bootloaderDir, "externals.js"),
54
- `const externals = [\n "solid-js",\n "solid-js/web",\n "solid-js/html",\n "solid-js/store",\n "solid-js/jsx-runtime",\n "solid-js/h"\n];\nexport default externals;\n`
55
- );
56
-
57
- const elementsDir = path.join(tmpDir, "packages/patchwork-elements");
58
- await fs.mkdir(elementsDir, { recursive: true });
59
- await fs.writeFile(
60
- path.join(elementsDir, "package.json"),
61
- JSON.stringify({
62
- name: "@inkandswitch/patchwork-elements",
63
- version: "0.0.1",
64
- type: "module",
65
- })
66
- );
67
-
68
- await fs.writeFile(
69
- path.join(tmpDir, "pnpm-workspace.yaml"),
70
- [
71
- "packages:",
72
- ' - "packages/*"',
73
- "configDependencies:",
74
- ' pnpm-plugin-patchwork: "0.1.0"',
75
- "",
76
- ].join("\n")
77
- );
78
-
79
- // .npmrc — avoid strict peer deps issues
80
- await fs.writeFile(
81
- path.join(tmpDir, ".npmrc"),
82
- "strict-peer-dependencies=false\nauto-install-peers=true\n"
83
- );
84
-
85
- // install — plugin resolves automerge: deps automatically
86
- // filter out parent pnpm's npm_config_* env vars to avoid contamination
87
- const cleanEnv = Object.fromEntries(
88
- Object.entries(process.env).filter(
89
- ([k]) => !k.startsWith("npm_")
90
- )
91
- );
92
- execSync("npx pnpm@next-11 install --no-frozen-lockfile", {
93
- cwd: tmpDir,
94
- stdio: "pipe",
95
- timeout: 120_000,
96
- env: {
97
- ...cleanEnv,
98
- PATCHWORK_SYNC_SERVER: "wss://sync3.automerge.org",
99
- },
100
- });
101
-
102
- // Phase 2: write source file
103
- const srcDir = path.join(tmpDir, "src");
104
- await fs.mkdir(srcDir, { recursive: true });
105
- await fs.writeFile(
106
- path.join(srcDir, "index.ts"),
107
- [
108
- `import { createSignal } from "solid-js";`,
109
- `import * as sideboard from "my-sideboard-tool";`,
110
- `console.log(createSignal, sideboard);`,
111
- "",
112
- ].join("\n")
113
- );
114
-
115
- process.chdir(tmpDir);
116
- }, 180_000);
117
-
118
- afterAll(async () => {
119
- process.chdir(originalCwd);
120
- await fs.rm(tmpDir, { recursive: true, force: true });
121
- });
122
-
123
- describe("pnpm plugin installed my-sideboard-tool from automerge", () => {
124
- it("has a package.json with the sideboard package name", async () => {
125
- const pkgPath = path.join(
126
- tmpDir,
127
- "node_modules/my-sideboard-tool/package.json"
128
- );
129
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
130
- expect(pkg.name).toBe("@chee/patchwork-sideboard");
131
- });
132
-
133
- it("has an exports field with a patchwork condition", async () => {
134
- const pkgPath = path.join(
135
- tmpDir,
136
- "node_modules/my-sideboard-tool/package.json"
137
- );
138
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
139
- expect(pkg.exports).toBeDefined();
140
- expect(pkg.exports["."]).toBeDefined();
141
- expect(pkg.exports["."].patchwork).toBeDefined();
142
- });
143
-
144
- it("contains actual dist files from the automerge document", async () => {
145
- const distDir = path.join(tmpDir, "node_modules/my-sideboard-tool/dist");
146
- const files = await fs.readdir(distDir);
147
- expect(files.length).toBeGreaterThan(0);
148
- // should have js output
149
- expect(files.some((f) => f.endsWith(".js"))).toBe(true);
150
- });
151
- });
152
-
153
- describe("vite build", () => {
154
- let output: string;
155
-
156
- beforeAll(async () => {
157
- // Determine what entry point the plugin will resolve to
158
- const pluginPath = path.resolve(originalCwd, "dist/vite.js");
159
- const { default: vitePlugin } = await import(pluginPath);
160
-
161
- const result = await viteBuild({
162
- root: tmpDir,
163
- logLevel: "silent",
164
- build: {
165
- lib: {
166
- entry: path.join(tmpDir, "src/index.ts"),
167
- formats: ["es"],
168
- fileName: "index",
169
- },
170
- outDir: path.join(tmpDir, "dist-vite"),
171
- write: true,
172
- minify: false,
173
- },
174
- plugins: [vitePlugin()],
175
- });
176
-
177
- // vite 7 outputs .js, older versions output .mjs
178
- const mjs = path.join(tmpDir, "dist-vite/index.mjs");
179
- const js = path.join(tmpDir, "dist-vite/index.js");
180
- output = await fs.readFile(
181
- await fs.access(mjs).then(() => mjs, () => js),
182
- "utf-8"
183
- );
184
- }, 60_000);
185
-
186
- it("rewrites automerge dep to service-worker URL", () => {
187
- expect(output).toContain(ENCODED_URL);
188
- // Should be an import from /<encoded-url>/...
189
- expect(output).toMatch(new RegExp(`from ["']/${ENCODED_URL}/`));
190
- });
191
-
192
- it("externalizes solid-js", () => {
193
- expect(output).toMatch(/from ["']solid-js["']/);
194
- // if solid-js were inlined, the output would be thousands of lines
195
- const lines = output.split("\n").length;
196
- expect(lines).toBeLessThan(20);
197
- });
198
-
199
- it("does not contain bundled automerge dep code", () => {
200
- // The automerge dep should be external, not inlined
201
- expect(output).not.toContain("my-sideboard-tool");
202
- });
203
- });
204
-
205
- describe("esbuild build", () => {
206
- let output: string;
207
-
208
- beforeAll(async () => {
209
- const pluginPath = path.resolve(originalCwd, "dist/esbuild.js");
210
- const { default: esbuildPlugin } = await import(pluginPath);
211
-
212
- await esbuild.build({
213
- entryPoints: [path.join(tmpDir, "src/index.ts")],
214
- bundle: true,
215
- format: "esm",
216
- outfile: path.join(tmpDir, "dist-esbuild/index.js"),
217
- plugins: [esbuildPlugin()],
218
- logLevel: "silent",
219
- });
220
-
221
- output = await fs.readFile(
222
- path.join(tmpDir, "dist-esbuild/index.js"),
223
- "utf-8"
224
- );
225
- }, 60_000);
226
-
227
- it("rewrites automerge dep to service-worker URL", () => {
228
- expect(output).toContain(ENCODED_URL);
229
- expect(output).toMatch(new RegExp(`from ["']/${ENCODED_URL}/`));
230
- });
231
-
232
- it("externalizes solid-js", () => {
233
- expect(output).toMatch(/from ["']solid-js["']/);
234
- // if solid-js were inlined, the output would be thousands of lines
235
- const lines = output.split("\n").length;
236
- expect(lines).toBeLessThan(20);
237
- });
238
-
239
- it("does not contain bundled automerge dep code", () => {
240
- expect(output).not.toContain("my-sideboard-tool");
241
- });
242
- });
243
- });
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "esnext",
4
- "module": "esnext",
5
- "moduleResolution": "bundler",
6
- "outDir": "dist",
7
- "declaration": true,
8
- "strict": true,
9
- "skipLibCheck": true,
10
- "isolatedModules": true
11
- },
12
- "include": ["src"]
13
- }
package/vitest.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: "node",
7
- include: ["test/**/*.{test,spec}.ts"],
8
- testTimeout: 180_000,
9
- },
10
- });