@pineforge/codegen-pyodide 0.7.0 → 0.7.1

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/glue.py ADDED
@@ -0,0 +1,35 @@
1
+ # CANONICAL GLUE — runtime-equivalent to the body of PY_GLUE in
2
+ # pineforge-app/apps/web/lib/pyodide-transpiler/glue.ts (produces identical
3
+ # transpile_json output). The browser worker and this gate run the same logic,
4
+ # so the gate's parity guarantee reflects shipped behavior.
5
+ # (Phase 3: ship this from the npm package so there is one source of truth.)
6
+ import json
7
+ import sys
8
+
9
+ if "/codegen" not in sys.path:
10
+ sys.path.insert(0, "/codegen")
11
+
12
+ from pineforge_codegen import transpile
13
+ from pineforge_codegen.errors import CompileError
14
+
15
+
16
+ def transpile_json(source: str) -> str:
17
+ try:
18
+ cpp = transpile(source)
19
+ except CompileError as e:
20
+ diags = []
21
+ for d in e.diagnostics:
22
+ loc = d.location
23
+ message = d.message + " — " + d.hint if getattr(d, "hint", None) else d.message
24
+ entry = {
25
+ "line": loc.line if loc else 1,
26
+ "col": loc.col if loc else 1,
27
+ "message": message,
28
+ "severity": getattr(d.level, "value", "error"),
29
+ }
30
+ end_col = getattr(loc, "end_col", None) if loc else None
31
+ if end_col is not None:
32
+ entry["endCol"] = end_col
33
+ diags.append(entry)
34
+ return json.dumps({"ok": False, "error": str(e), "diagnostics": diags})
35
+ return json.dumps({"ok": True, "cpp": cpp})
package/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  // Entry for @pineforge/codegen-pyodide. Resolves the packaged payload paths and
2
2
  // metadata so consumers (the app's build + Node oracle/grammar tooling) read
3
3
  // everything from this one dependency — no git submodule.
4
+ import { readFileSync } from "node:fs";
4
5
  import { createRequire } from "node:module";
5
6
  import { dirname, join } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
@@ -18,3 +19,12 @@ export const archivePath = join(HERE, `pineforge_codegen-${release.codegen}.tar.
18
19
  // so `import pineforge_codegen` resolves (oracle tests, grammar gen).
19
20
  export const sourceRoot = HERE;
20
21
  export const codegenSourceDir = join(HERE, "pineforge_codegen");
22
+
23
+ // Absolute path to the shipped ESM module worker — consumers copy it into their
24
+ // served /pyodide/ dir and load it via `new Worker(url, { type: "module" })`.
25
+ export const workerPath = join(HERE, "transpile.worker.mjs");
26
+
27
+ // Canonical Pyodide glue source (single source: gate/glue.py) — runPython'd to
28
+ // define transpile_json. The worker embeds this verbatim; exported here too so
29
+ // Node consumers (parity tests) run the exact same glue.
30
+ export const glue = readFileSync(join(HERE, "glue.py"), "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pineforge/codegen-pyodide",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Gate-validated Pyodide payload for the PineScript v6 -> C++ transpiler: archive (run in Pyodide), unpacked source, introspected tables, and release metadata.",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
@@ -14,7 +14,9 @@
14
14
  "pineforge_codegen-*.tar.gz",
15
15
  "pineforge_codegen/**/*",
16
16
  "tables.json",
17
- "release.json"
17
+ "release.json",
18
+ "transpile.worker.mjs",
19
+ "glue.py"
18
20
  ],
19
21
  "engines": {
20
22
  "node": ">=20"
@@ -22,7 +24,12 @@
22
24
  "peerDependencies": {
23
25
  "pyodide": "314.0.0"
24
26
  },
25
- "keywords": ["pinescript", "transpiler", "pyodide", "codegen"],
27
+ "keywords": [
28
+ "pinescript",
29
+ "transpiler",
30
+ "pyodide",
31
+ "codegen"
32
+ ],
26
33
  "homepage": "https://github.com/pineforge-4pass/pineforge-codegen-oss",
27
34
  "repository": {
28
35
  "type": "git",
Binary file
package/release.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "codegen": "0.7.0",
2
+ "codegen": "0.7.1",
3
3
  "pyodide": "314.0.0",
4
4
  "python": "3.14.0",
5
5
  "emscripten": "emscripten_5_0_3",
6
- "sha256": "1ff4cb99ed80119cb6a9bd639bec1c2a6739654320fb4cf2f6fa37a8d4441710"
6
+ "sha256": "51be5f3cdecf1ce87685bdd570d60f8c15e5fa0c4ce3b2e9e2594eefe89babd0"
7
7
  }
package/tables.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "CODEGEN_VERSION": "0.7.0",
2
+ "CODEGEN_VERSION": "0.7.1",
3
3
  "TA_CLASS_MAP_KEYS": [
4
4
  "sma",
5
5
  "ema",
@@ -0,0 +1,84 @@
1
+ // GENERATED by scripts/build-npm-package.mjs from gate/glue.py — DO NOT EDIT.
2
+ // Canonical client-side Pyodide transpile worker, shipped by
3
+ // @pineforge/codegen-pyodide. Consumers serve it from /pyodide/ and load it via:
4
+ // new Worker("/pyodide/transpile.worker.mjs", { type: "module" })
5
+ // A real ESM module worker (string-URL load bypasses the bundler), so Pyodide
6
+ // 314's module-worker requirement is satisfied. Mirrors the old in-app
7
+ // transpile.worker.ts; protocol is identical (see app protocol.ts).
8
+ import { loadPyodide } from "/pyodide/pyodide.mjs";
9
+
10
+ const GLUE = `# CANONICAL GLUE — runtime-equivalent to the body of PY_GLUE in
11
+ # pineforge-app/apps/web/lib/pyodide-transpiler/glue.ts (produces identical
12
+ # transpile_json output). The browser worker and this gate run the same logic,
13
+ # so the gate's parity guarantee reflects shipped behavior.
14
+ # (Phase 3: ship this from the npm package so there is one source of truth.)
15
+ import json
16
+ import sys
17
+
18
+ if "/codegen" not in sys.path:
19
+ sys.path.insert(0, "/codegen")
20
+
21
+ from pineforge_codegen import transpile
22
+ from pineforge_codegen.errors import CompileError
23
+
24
+
25
+ def transpile_json(source: str) -> str:
26
+ try:
27
+ cpp = transpile(source)
28
+ except CompileError as e:
29
+ diags = []
30
+ for d in e.diagnostics:
31
+ loc = d.location
32
+ message = d.message + " — " + d.hint if getattr(d, "hint", None) else d.message
33
+ entry = {
34
+ "line": loc.line if loc else 1,
35
+ "col": loc.col if loc else 1,
36
+ "message": message,
37
+ "severity": getattr(d.level, "value", "error"),
38
+ }
39
+ end_col = getattr(loc, "end_col", None) if loc else None
40
+ if end_col is not None:
41
+ entry["endCol"] = end_col
42
+ diags.append(entry)
43
+ return json.dumps({"ok": False, "error": str(e), "diagnostics": diags})
44
+ return json.dumps({"ok": True, "cpp": cpp})
45
+ `;
46
+
47
+ const post = (m) => self.postMessage(m);
48
+ let transpileJson = null;
49
+
50
+ async function init() {
51
+ try {
52
+ const pyodide = await loadPyodide({ indexURL: "/pyodide/" });
53
+ const manifestRes = await fetch("/pyodide/manifest.json", { cache: "no-cache" });
54
+ if (!manifestRes.ok) throw new Error(`fetch /pyodide/manifest.json: ${manifestRes.status}`);
55
+ const manifest = await manifestRes.json();
56
+ const archiveRes = await fetch(`/pyodide/${manifest.archive}`);
57
+ if (!archiveRes.ok) throw new Error(`fetch /pyodide/${manifest.archive}: ${archiveRes.status}`);
58
+ const buf = await archiveRes.arrayBuffer();
59
+ pyodide.unpackArchive(buf, "gztar", { extractDir: "/codegen" });
60
+ pyodide.runPython(GLUE);
61
+ const fn = pyodide.globals.get("transpile_json");
62
+ transpileJson = (source) => fn(source);
63
+ post({ type: "ready", codegenVersion: manifest.codegenVersion });
64
+ } catch (err) {
65
+ post({ type: "init-error", error: err instanceof Error ? err.message : String(err) });
66
+ }
67
+ }
68
+
69
+ const initPromise = init();
70
+
71
+ self.onmessage = async (ev) => {
72
+ const { id, source } = ev.data;
73
+ await initPromise;
74
+ if (!transpileJson) {
75
+ post({ type: "crash", id, error: "pyodide failed to initialize" });
76
+ return;
77
+ }
78
+ try {
79
+ const result = JSON.parse(transpileJson(source));
80
+ post({ type: "result", id, result });
81
+ } catch (err) {
82
+ post({ type: "crash", id, error: err instanceof Error ? err.message : String(err) });
83
+ }
84
+ };
Binary file