@frockbot/applet-sdk 0.7.126 → 0.7.128

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/applet-sdk",
3
- "version": "0.7.126",
3
+ "version": "0.7.128",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Authoring SDK for FrockBot Applets: schema-first Durable Object server, TanStack DB client, component kit, linter, and the build pipeline.",
@@ -21,13 +21,12 @@
21
21
  */
22
22
 
23
23
  import { createHash, randomUUID } from "node:crypto";
24
- import { realpathSync } from "node:fs";
25
- import { relative, resolve } from "node:path";
26
24
 
27
- import { build as esbuild, type Metafile } from "esbuild";
25
+ import { build as esbuild } from "esbuild";
28
26
 
29
27
  import type { AppletDescriptionV1 } from "../server/applet.js";
30
28
  import { readDescriptor, type AppletBuildManifestV1 } from "./manifest.js";
29
+ import { stableModulePaths } from "./module-paths.js";
31
30
  import { bundlerNodePaths, SDK_ENTRIES, SDK_ROOT } from "./paths.js";
32
31
  import { withOneMoreBoot } from "./boot.js";
33
32
  import { startAppletRuntime } from "./runtime.js";
@@ -42,66 +41,6 @@ function sha256(text: string): string {
42
41
  return createHash("sha256").update(text, "utf8").digest("hex");
43
42
  }
44
43
 
45
- /** esbuild reports real paths; a temp directory is often a symlink to one. */
46
- function real(path: string): string {
47
- try {
48
- return realpathSync(resolve(path));
49
- } catch {
50
- return resolve(path);
51
- }
52
- }
53
-
54
- /**
55
- * Where a bundled module came from, said the same way everywhere.
56
- *
57
- * An Applet's directory, the SDK's directory and the working directory are all
58
- * different on an author's machine and in the build container — and esbuild
59
- * writes each module's path into the unminified output as a comment. Left
60
- * alone, identical source bundled in two places produces two different files
61
- * and therefore two different content hashes, which would give R2 a new object
62
- * on every publish of unchanged code.
63
- */
64
- function moduleLabel(absolute: string, appletRoot: string): string {
65
- for (const [root, prefix] of [
66
- [appletRoot, ""],
67
- [real(SDK_ROOT), "applet-sdk/"],
68
- ] as const) {
69
- const inside = relative(root, absolute);
70
- if (!inside.startsWith("..") && inside !== "") return prefix + inside;
71
- }
72
- const dependency = absolute.lastIndexOf("node_modules/");
73
- return dependency === -1 ? absolute : absolute.slice(dependency);
74
- }
75
-
76
- /**
77
- * Rewrites esbuild's module comments to those stable labels.
78
- *
79
- * The paths come from the metafile rather than from a pattern over the output,
80
- * so only lines esbuild actually wrote are touched and a comment in someone's
81
- * own source is left alone.
82
- */
83
- function stableModulePaths(
84
- text: string,
85
- metafile: Metafile,
86
- appletRoot: string,
87
- ): string {
88
- const root = real(appletRoot);
89
- const labels = new Map(
90
- Object.keys(metafile.inputs).map((input) => [
91
- input,
92
- moduleLabel(real(input), root),
93
- ]),
94
- );
95
- return text
96
- .split("\n")
97
- .map((line) => {
98
- if (!line.startsWith("// ")) return line;
99
- const label = labels.get(line.slice(3));
100
- return label === undefined ? line : `// ${label}`;
101
- })
102
- .join("\n");
103
- }
104
-
105
44
  async function bundle(options: {
106
45
  stdin: string;
107
46
  resolveDir: string;
@@ -136,7 +75,9 @@ async function bundle(options: {
136
75
  });
137
76
  const file = result.outputFiles?.[0];
138
77
  if (!file) throw new Error("The bundler produced no output");
139
- return stableModulePaths(file.text, result.metafile, options.resolveDir);
78
+ return stableModulePaths(file.text, result.metafile, options.resolveDir, [
79
+ { directory: SDK_ROOT, prefix: "applet-sdk/" },
80
+ ]);
140
81
  }
141
82
 
142
83
  function page(title: string, script: string): string {
@@ -0,0 +1,72 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+
4
+ import type { Metafile } from "esbuild";
5
+
6
+ export interface StableModuleRoot {
7
+ directory: string;
8
+ prefix: string;
9
+ }
10
+
11
+ /** esbuild reports real paths; a temp directory is often a symlink to one. */
12
+ function real(path: string): string {
13
+ try {
14
+ return realpathSync(resolve(path));
15
+ } catch {
16
+ return resolve(path);
17
+ }
18
+ }
19
+
20
+ function portable(path: string): string {
21
+ return path.split(sep).join("/").replaceAll("\\", "/");
22
+ }
23
+
24
+ function moduleLabel(absolute: string, roots: readonly StableModuleRoot[]) {
25
+ for (const { directory, prefix } of roots) {
26
+ const inside = relative(directory, absolute);
27
+ const outside =
28
+ inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside);
29
+ if (!outside && inside !== "") return prefix + portable(inside);
30
+ }
31
+ const path = portable(absolute);
32
+ const dependency = path.lastIndexOf("node_modules/");
33
+ return dependency === -1 ? path : path.slice(dependency);
34
+ }
35
+
36
+ /**
37
+ * Rewrites esbuild's module comments to location-independent labels.
38
+ *
39
+ * The paths come from the metafile rather than from a pattern over the output,
40
+ * so only lines esbuild actually wrote are touched and a comment in authored
41
+ * source is left alone. Callers name any additional source roots that belong
42
+ * to their artifact; dependencies under `node_modules` are stable by package
43
+ * path automatically.
44
+ */
45
+ export function stableModulePaths(
46
+ text: string,
47
+ metafile: Metafile,
48
+ sourceRoot: string,
49
+ additionalRoots: readonly StableModuleRoot[] = [],
50
+ ): string {
51
+ const roots = [
52
+ { directory: real(sourceRoot), prefix: "" },
53
+ ...additionalRoots.map(({ directory, prefix }) => ({
54
+ directory: real(directory),
55
+ prefix,
56
+ })),
57
+ ];
58
+ const labels = new Map(
59
+ Object.keys(metafile.inputs).map((input) => [
60
+ input,
61
+ moduleLabel(real(input), roots),
62
+ ]),
63
+ );
64
+ return text
65
+ .split("\n")
66
+ .map((line) => {
67
+ if (!line.startsWith("// ")) return line;
68
+ const label = labels.get(line.slice(3));
69
+ return label === undefined ? line : `// ${label}`;
70
+ })
71
+ .join("\n");
72
+ }
@@ -30,6 +30,7 @@ import ts from "typescript";
30
30
 
31
31
  import type { AppletDiagnostic } from "../lint/index.js";
32
32
  import { bootedWithin, withOneMoreBoot } from "./boot.js";
33
+ import { stableModulePaths } from "./module-paths.js";
33
34
  import { APPLET_COMPATIBILITY_DATE } from "./runtime.js";
34
35
  import { SDK_PLUGIN_TYPES } from "./paths.js";
35
36
 
@@ -241,20 +242,12 @@ export async function bundlePlugin(directory: string): Promise<string> {
241
242
  },
242
243
  ],
243
244
  define: { "process.env.NODE_ENV": '"production"' },
245
+ metafile: true,
244
246
  logLevel: "silent",
245
247
  });
246
248
  const file = result.outputFiles?.[0];
247
249
  if (!file) throw new Error("The bundler produced no output");
248
- // The bundle's module comments carry the temp directory; keep the artifact a
249
- // function of the source alone, as the Applet build does.
250
- return file.text
251
- .split("\n")
252
- .map((line) =>
253
- line.startsWith("// ") && line.includes(directory)
254
- ? `// ${relative(directory, line.slice(3))}`
255
- : line,
256
- )
257
- .join("\n");
250
+ return stableModulePaths(file.text, result.metafile, directory);
258
251
  }
259
252
 
260
253
  /**