@cosmicdrift/kumiko-server-runtime 0.194.0 → 0.196.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-server-runtime",
3
- "version": "0.194.0",
3
+ "version": "0.196.0",
4
4
  "description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold/codegen tooling.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -76,8 +76,8 @@
76
76
  }
77
77
  },
78
78
  "dependencies": {
79
- "@cosmicdrift/kumiko-bundled-features": "0.194.0",
80
- "@cosmicdrift/kumiko-framework": "0.194.0",
79
+ "@cosmicdrift/kumiko-bundled-features": "0.196.0",
80
+ "@cosmicdrift/kumiko-framework": "0.196.0",
81
81
  "temporal-polyfill": "^0.3.2"
82
82
  },
83
83
  "publishConfig": {
@@ -1,8 +1,8 @@
1
- // injectSchema teilt sich dev-server (every HTML response) und prod-
2
- // server (static-fallback index.html) Pfad. Bug hier wäre stiller
3
- // Production-Fail: createKumikoApp findet `window.__KUMIKO_SCHEMA__`
4
- // nicht und mountet leer. Tests pinnen die Idempotenz + die zwei
5
- // Insertion-Punkte (vor /client.js-Tag oder vor </body>).
1
+ // injectSchema is shared by the dev-server (every HTML response) and
2
+ // prod-server (static-fallback index.html) paths. A bug here would be a
3
+ // silent production failure: createKumikoApp wouldn't find
4
+ // `window.__KUMIKO_SCHEMA__` and would mount empty. Tests pin idempotency
5
+ // plus the two insertion points (before the /client.js tag or before </body>).
6
6
 
7
7
  import { describe, expect, test } from "bun:test";
8
8
  import { injectSchema } from "../inject-schema";
@@ -15,8 +15,8 @@ describe("injectSchema", () => {
15
15
  const html = '<html><body><script src="/client.js" defer></script></body></html>';
16
16
  const out = injectSchema(html, SCHEMA);
17
17
  expect(out).toContain(TAG);
18
- // Schema MUSS vor dem Client-Bundle stehensonst läuft
19
- // createKumikoApp() bevor window.__KUMIKO_SCHEMA__ gesetzt ist.
18
+ // Schema MUST come before the client bundle otherwise createKumikoApp()
19
+ // runs before window.__KUMIKO_SCHEMA__ is set.
20
20
  expect(out.indexOf(TAG)).toBeLessThan(out.indexOf('<script src="/client.js"'));
21
21
  });
22
22
 
@@ -45,8 +45,8 @@ describe("injectSchema", () => {
45
45
  test("Idempotent: bei bereits injectem Schema kein zweiter Tag", () => {
46
46
  const html = `<html><body>${TAG}</body></html>`;
47
47
  const out = injectSchema(html, '{"features":[{"differentSchema":true}]}');
48
- // Original-Tag bleibt, kein zweiter Tag hinzugefügtder Marker-
49
- // Check verhindert sonst stacking-Tags bei repeated reads.
48
+ // Original tag stays, no second tag addedthe marker check otherwise
49
+ // prevents stacking tags on repeated reads.
50
50
  expect(out).toBe(html);
51
51
  });
52
52
 
@@ -62,10 +62,39 @@ describe("injectSchema", () => {
62
62
  });
63
63
  const html = '<html><body><script src="/client.js"></script></body></html>';
64
64
  const out = injectSchema(html, complex);
65
- // Sanity: das injected Skript muss valid JS sein (Object-Literal-
66
- // Syntax, keine HTML-Reserved-Chars im JSON die den <script>-Block
67
- // brechen würden — JSON.stringify entkommt /, < usw. nicht, aber
68
- // die Standard-Chars die wir hier nutzen sind unkritisch).
65
+ // Sanity: the injected script must be valid JS (object-literal syntax,
66
+ // no HTML-reserved chars in the JSON that would break the <script>
67
+ // block — JSON.stringify doesn't escape /, < etc., but the standard
68
+ // chars used here are harmless).
69
69
  expect(out).toContain(`window.__KUMIKO_SCHEMA__=${complex}`);
70
70
  });
71
+
72
+ test("Schema mit $-Replacement-Patterns bleibt byte-identisch (kein HTML-Splicing)", () => {
73
+ // String.prototype.replace() interprets $$, $&, $`, $' specially in the
74
+ // REPLACEMENT argument. The injected schema JSON string lands there — an
75
+ // i18n label containing "$'" or similar would otherwise splice parts of
76
+ // `html` into the script tag. A replacer-function argument sidesteps that.
77
+ const dollarSchema = '{"label":"$\' tail $$ amp $& end"}';
78
+ const dollarTag = `<script>window.__KUMIKO_SCHEMA__=${dollarSchema};</script>`;
79
+ const html = '<html><body><script src="/client.js"></script></body></html>';
80
+ const out = injectSchema(html, dollarSchema);
81
+ // Insertion point is right before the /client.js tag, not the start of
82
+ // the document — built via indexOf/slice, not .replace(), so the
83
+ // expected value doesn't itself run through the same $-pattern footgun.
84
+ const clientScriptIdx = html.indexOf('<script src="/client.js"');
85
+ const expected = html.slice(0, clientScriptIdx) + dollarTag + html.slice(clientScriptIdx);
86
+ expect(out).toBe(expected);
87
+ });
88
+
89
+ test("Schema mit $-Replacement-Patterns, Insertion vor </body>", () => {
90
+ const dollarSchema = '{"label":"$\' tail $$ amp $& end"}';
91
+ const dollarTag = `<script>window.__KUMIKO_SCHEMA__=${dollarSchema};</script>`;
92
+ const html = "<html><body><div id=root></div></body></html>";
93
+ const out = injectSchema(html, dollarSchema);
94
+ // Built via slice/concat, not .replace() — the expected value must not
95
+ // itself run through the same $-pattern footgun the fix removes.
96
+ const bodyIdx = html.indexOf("</body>");
97
+ const expected = html.slice(0, bodyIdx) + dollarTag + html.slice(bodyIdx);
98
+ expect(out).toBe(expected);
99
+ });
71
100
  });
@@ -27,7 +27,7 @@ export function injectSchema(html: string, schemaJson: string): string {
27
27
  const tag = `<script>window.__KUMIKO_SCHEMA__=${scriptSafeJsonHtml(schemaJson)};</script>`;
28
28
  const clientScriptMatch = html.match(CLIENT_SCRIPT_TAG_RE);
29
29
  if (clientScriptMatch) {
30
- return html.replace(clientScriptMatch[0], `${tag}${clientScriptMatch[0]}`);
30
+ return html.replace(CLIENT_SCRIPT_TAG_RE, (m) => `${tag}${m}`);
31
31
  }
32
- return html.includes("</body>") ? html.replace("</body>", `${tag}</body>`) : html + tag;
32
+ return html.includes("</body>") ? html.replace("</body>", () => `${tag}</body>`) : html + tag;
33
33
  }