react-pebble 0.1.0 → 0.1.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.
@@ -1,3 +1,3 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`node:child_process`),t=require(`node:path`),n=require(`node:url`),r=require(`node:fs`);var i=(0,t.dirname)((0,n.fileURLToPath)({}.url));async function a(n){let a=n.logger??(()=>{}),o=n.projectRoot??process.cwd(),s=(0,t.basename)((0,t.resolve)(o,n.entry)).replace(/\.[jt]sx?$/,``),c=(0,t.resolve)(i,`../../scripts/compile-to-piu.ts`);if(!(0,r.existsSync)(c))throw Error(`Compiler script not found at ${c}`);a(`Compiling ${s}...`);let l={...process.env,EXAMPLE:s};n.settleMs&&(l.SETTLE_MS=String(n.settleMs)),n.platform&&(l.PEBBLE_PLATFORM=n.platform);let u,d;try{u=(0,e.execSync)(`npx tsx "${c}"`,{cwd:o,env:l,encoding:`utf-8`,timeout:3e4,stdio:[`pipe`,`pipe`,`pipe`]});try{d=(0,e.execSync)(`npx tsx "${c}" 2>&1 1>/dev/null`,{cwd:o,env:l,encoding:`utf-8`,timeout:3e4})}catch{d=``}}catch(e){let t=e;throw Error(`Compilation failed for ${s}: ${t.stderr??t.message}`)}let f=d.includes(`Button bindings discovered:`)&&!d.includes(`Button bindings discovered: 0`),p=[],m=d.match(/useMessage detected: key="([^"]+)"/);return m?.[1]&&p.push(m[1]),a(`Compiled ${s}: ${u.split(`
2
- `).length} lines, buttons=${f}, messageKeys=[${p.join(`,`)}]`),{code:u,hasButtons:f,messageKeys:p,diagnostics:d}}exports.compileToPiu=a;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`node:child_process`),t=require(`node:path`),n=require(`node:url`),r=require(`node:fs`);var i=(0,t.dirname)((0,n.fileURLToPath)({}.url));async function a(n){let a=n.logger??(()=>{}),o=n.projectRoot??process.cwd(),s=(0,t.resolve)(o,n.entry),c=(0,t.basename)(s).replace(/\.[jt]sx?$/,``),l=(0,t.resolve)(i,`../../scripts/compile-to-piu.ts`);if(!(0,r.existsSync)(l))throw Error(`Compiler script not found at ${l}`);a(`Compiling ${c}...`);let u={...process.env,EXAMPLE:s};n.settleMs&&(u.SETTLE_MS=String(n.settleMs)),n.platform&&(u.PEBBLE_PLATFORM=n.platform);let d,f;try{d=(0,e.execSync)(`npx tsx "${l}"`,{cwd:o,env:u,encoding:`utf-8`,timeout:3e4,stdio:[`pipe`,`pipe`,`pipe`]});try{f=(0,e.execSync)(`npx tsx "${l}" 2>&1 1>/dev/null`,{cwd:o,env:u,encoding:`utf-8`,timeout:3e4})}catch{f=``}}catch(e){let t=e;throw Error(`Compilation failed for ${c}: ${t.stderr??t.message}`)}let p=f.includes(`Button bindings discovered:`)&&!f.includes(`Button bindings discovered: 0`),m=[],h=f.match(/useMessage detected: key="([^"]+)"/);return h?.[1]&&m.push(h[1]),a(`Compiled ${c}: ${d.split(`
2
+ `).length} lines, buttons=${p}, messageKeys=[${m.join(`,`)}]`),{code:d,hasButtons:p,messageKeys:m,diagnostics:f}}exports.compileToPiu=a;
3
3
  //# sourceMappingURL=compiler.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.cjs","names":[],"sources":["../../src/compiler/index.ts"],"sourcesContent":["/**\n * src/compiler/index.ts — react-pebble compile-to-piu library API.\n *\n * Wraps the compile-to-piu.ts script as a programmatic API. The script\n * runs as a subprocess (it uses module-level state that requires process\n * isolation). A future refactoring will inline the logic as a pure function.\n *\n * Usage:\n * import { compileToPiu } from 'react-pebble/compiler';\n * const result = await compileToPiu({ entry: 'examples/watchface.tsx' });\n * console.log(result.code); // piu Application.template JS\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve, dirname, basename } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { existsSync } from 'node:fs';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface CompileOptions {\n /** Path to the entry .tsx file (relative to cwd or absolute) */\n entry: string;\n /** Milliseconds to wait for async effects (useEffect/setTimeout) */\n settleMs?: number;\n /** Target platform (default: 'emery') */\n platform?: string;\n /** Logger for diagnostic messages */\n logger?: (msg: string) => void;\n /** Project root directory (default: process.cwd()) */\n projectRoot?: string;\n}\n\nexport interface CompileResult {\n /** The compiled piu JavaScript code */\n code: string;\n /** Whether the component uses useButton (needs watchapp mode) */\n hasButtons: boolean;\n /** Message keys used by useMessage hooks */\n messageKeys: string[];\n /** Diagnostic messages from the compiler */\n diagnostics: string;\n}\n\n/**\n * Compile a Preact component to piu Application.template code.\n *\n * Internally runs scripts/compile-to-piu.ts as a subprocess.\n */\nexport async function compileToPiu(options: CompileOptions): Promise<CompileResult> {\n const log = options.logger ?? (() => {});\n const projectRoot = options.projectRoot ?? process.cwd();\n\n // Resolve the entry to an example name (for backwards compat with the script)\n const entryPath = resolve(projectRoot, options.entry);\n const exampleName = basename(entryPath).replace(/\\.[jt]sx?$/, '');\n\n // Find the compiler script\n const scriptPath = resolve(__dirname, '../../scripts/compile-to-piu.ts');\n if (!existsSync(scriptPath)) {\n throw new Error(`Compiler script not found at ${scriptPath}`);\n }\n\n log(`Compiling ${exampleName}...`);\n\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n EXAMPLE: exampleName,\n };\n if (options.settleMs) {\n env.SETTLE_MS = String(options.settleMs);\n }\n if (options.platform) {\n env.PEBBLE_PLATFORM = options.platform;\n }\n\n // Run the compiler script and capture stdout (code) + stderr (diagnostics)\n let code: string;\n let diagnostics: string;\n try {\n code = execSync(`npx tsx \"${scriptPath}\"`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Re-run to capture stderr separately (execSync doesn't give both easily)\n try {\n diagnostics = execSync(`npx tsx \"${scriptPath}\" 2>&1 1>/dev/null`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n });\n } catch {\n diagnostics = '';\n }\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw new Error(`Compilation failed for ${exampleName}: ${e.stderr ?? e.message}`);\n }\n\n // Parse diagnostics to extract metadata\n const hasButtons = diagnostics.includes('Button bindings discovered:') &&\n !diagnostics.includes('Button bindings discovered: 0');\n const messageKeys: string[] = [];\n const msgMatch = diagnostics.match(/useMessage detected: key=\"([^\"]+)\"/);\n if (msgMatch?.[1]) messageKeys.push(msgMatch[1]);\n\n log(`Compiled ${exampleName}: ${code.split('\\n').length} lines, buttons=${hasButtons}, messageKeys=[${messageKeys.join(',')}]`);\n\n return { code, hasButtons, messageKeys, diagnostics };\n}\n"],"mappings":"yKAkBA,IAAM,GAAA,EAAA,EAAA,UAAA,EAAA,EAAA,eAAA,EAAA,CAA8C,IAAI,CAAC,CA+BzD,eAAsB,EAAa,EAAiD,CAClF,IAAM,EAAM,EAAQ,aAAiB,IAC/B,EAAc,EAAQ,aAAe,QAAQ,KAAK,CAIlD,GAAA,EAAA,EAAA,WAAA,EAAA,EAAA,SADoB,EAAa,EAAQ,MAAM,CACd,CAAC,QAAQ,aAAc,GAAG,CAG3D,GAAA,EAAA,EAAA,SAAqB,EAAW,kCAAkC,CACxE,GAAI,EAAA,EAAA,EAAA,YAAY,EAAW,CACzB,MAAU,MAAM,gCAAgC,IAAa,CAG/D,EAAI,aAAa,EAAY,KAAK,CAElC,IAAM,EAA8B,CAClC,GAAG,QAAQ,IACX,QAAS,EACV,CACG,EAAQ,WACV,EAAI,UAAY,OAAO,EAAQ,SAAS,EAEtC,EAAQ,WACV,EAAI,gBAAkB,EAAQ,UAIhC,IAAI,EACA,EACJ,GAAI,CACF,GAAA,EAAA,EAAA,UAAgB,YAAY,EAAW,GAAI,CACzC,IAAK,EACL,MACA,SAAU,QACV,QAAS,IACT,MAAO,CAAC,OAAQ,OAAQ,OAAO,CAChC,CAAC,CAEF,GAAI,CACF,GAAA,EAAA,EAAA,UAAuB,YAAY,EAAW,oBAAqB,CACjE,IAAK,EACL,MACA,SAAU,QACV,QAAS,IACV,CAAC,MACI,CACN,EAAc,UAET,EAAK,CACZ,IAAM,EAAI,EACV,MAAU,MAAM,0BAA0B,EAAY,IAAI,EAAE,QAAU,EAAE,UAAU,CAIpF,IAAM,EAAa,EAAY,SAAS,8BAA8B,EACpE,CAAC,EAAY,SAAS,gCAAgC,CAClD,EAAwB,EAAE,CAC1B,EAAW,EAAY,MAAM,qCAAqC,CAKxE,OAJI,IAAW,IAAI,EAAY,KAAK,EAAS,GAAG,CAEhD,EAAI,YAAY,EAAY,IAAI,EAAK,MAAM;EAAK,CAAC,OAAO,kBAAkB,EAAW,iBAAiB,EAAY,KAAK,IAAI,CAAC,GAAG,CAExH,CAAE,OAAM,aAAY,cAAa,cAAa"}
1
+ {"version":3,"file":"compiler.cjs","names":[],"sources":["../../src/compiler/index.ts"],"sourcesContent":["/**\n * src/compiler/index.ts — react-pebble compile-to-piu library API.\n *\n * Wraps the compile-to-piu.ts script as a programmatic API. The script\n * runs as a subprocess (it uses module-level state that requires process\n * isolation). A future refactoring will inline the logic as a pure function.\n *\n * Usage:\n * import { compileToPiu } from 'react-pebble/compiler';\n * const result = await compileToPiu({ entry: 'examples/watchface.tsx' });\n * console.log(result.code); // piu Application.template JS\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve, dirname, basename } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { existsSync } from 'node:fs';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface CompileOptions {\n /** Path to the entry .tsx file (relative to cwd or absolute) */\n entry: string;\n /** Milliseconds to wait for async effects (useEffect/setTimeout) */\n settleMs?: number;\n /** Target platform (default: 'emery') */\n platform?: string;\n /** Logger for diagnostic messages */\n logger?: (msg: string) => void;\n /** Project root directory (default: process.cwd()) */\n projectRoot?: string;\n}\n\nexport interface CompileResult {\n /** The compiled piu JavaScript code */\n code: string;\n /** Whether the component uses useButton (needs watchapp mode) */\n hasButtons: boolean;\n /** Message keys used by useMessage hooks */\n messageKeys: string[];\n /** Diagnostic messages from the compiler */\n diagnostics: string;\n}\n\n/**\n * Compile a Preact component to piu Application.template code.\n *\n * Internally runs scripts/compile-to-piu.ts as a subprocess.\n */\nexport async function compileToPiu(options: CompileOptions): Promise<CompileResult> {\n const log = options.logger ?? (() => {});\n const projectRoot = options.projectRoot ?? process.cwd();\n\n // Resolve the entry path pass the full path so the script can find it\n // whether it's an internal example or an external project file\n const entryPath = resolve(projectRoot, options.entry);\n const exampleName = basename(entryPath).replace(/\\.[jt]sx?$/, '');\n\n // Find the compiler script\n const scriptPath = resolve(__dirname, '../../scripts/compile-to-piu.ts');\n if (!existsSync(scriptPath)) {\n throw new Error(`Compiler script not found at ${scriptPath}`);\n }\n\n log(`Compiling ${exampleName}...`);\n\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n EXAMPLE: entryPath,\n };\n if (options.settleMs) {\n env.SETTLE_MS = String(options.settleMs);\n }\n if (options.platform) {\n env.PEBBLE_PLATFORM = options.platform;\n }\n\n // Run the compiler script and capture stdout (code) + stderr (diagnostics)\n let code: string;\n let diagnostics: string;\n try {\n code = execSync(`npx tsx \"${scriptPath}\"`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Re-run to capture stderr separately (execSync doesn't give both easily)\n try {\n diagnostics = execSync(`npx tsx \"${scriptPath}\" 2>&1 1>/dev/null`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n });\n } catch {\n diagnostics = '';\n }\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw new Error(`Compilation failed for ${exampleName}: ${e.stderr ?? e.message}`);\n }\n\n // Parse diagnostics to extract metadata\n const hasButtons = diagnostics.includes('Button bindings discovered:') &&\n !diagnostics.includes('Button bindings discovered: 0');\n const messageKeys: string[] = [];\n const msgMatch = diagnostics.match(/useMessage detected: key=\"([^\"]+)\"/);\n if (msgMatch?.[1]) messageKeys.push(msgMatch[1]);\n\n log(`Compiled ${exampleName}: ${code.split('\\n').length} lines, buttons=${hasButtons}, messageKeys=[${messageKeys.join(',')}]`);\n\n return { code, hasButtons, messageKeys, diagnostics };\n}\n"],"mappings":"yKAkBA,IAAM,GAAA,EAAA,EAAA,UAAA,EAAA,EAAA,eAAA,EAAA,CAA8C,IAAI,CAAC,CA+BzD,eAAsB,EAAa,EAAiD,CAClF,IAAM,EAAM,EAAQ,aAAiB,IAC/B,EAAc,EAAQ,aAAe,QAAQ,KAAK,CAIlD,GAAA,EAAA,EAAA,SAAoB,EAAa,EAAQ,MAAM,CAC/C,GAAA,EAAA,EAAA,UAAuB,EAAU,CAAC,QAAQ,aAAc,GAAG,CAG3D,GAAA,EAAA,EAAA,SAAqB,EAAW,kCAAkC,CACxE,GAAI,EAAA,EAAA,EAAA,YAAY,EAAW,CACzB,MAAU,MAAM,gCAAgC,IAAa,CAG/D,EAAI,aAAa,EAAY,KAAK,CAElC,IAAM,EAA8B,CAClC,GAAG,QAAQ,IACX,QAAS,EACV,CACG,EAAQ,WACV,EAAI,UAAY,OAAO,EAAQ,SAAS,EAEtC,EAAQ,WACV,EAAI,gBAAkB,EAAQ,UAIhC,IAAI,EACA,EACJ,GAAI,CACF,GAAA,EAAA,EAAA,UAAgB,YAAY,EAAW,GAAI,CACzC,IAAK,EACL,MACA,SAAU,QACV,QAAS,IACT,MAAO,CAAC,OAAQ,OAAQ,OAAO,CAChC,CAAC,CAEF,GAAI,CACF,GAAA,EAAA,EAAA,UAAuB,YAAY,EAAW,oBAAqB,CACjE,IAAK,EACL,MACA,SAAU,QACV,QAAS,IACV,CAAC,MACI,CACN,EAAc,UAET,EAAK,CACZ,IAAM,EAAI,EACV,MAAU,MAAM,0BAA0B,EAAY,IAAI,EAAE,QAAU,EAAE,UAAU,CAIpF,IAAM,EAAa,EAAY,SAAS,8BAA8B,EACpE,CAAC,EAAY,SAAS,gCAAgC,CAClD,EAAwB,EAAE,CAC1B,EAAW,EAAY,MAAM,qCAAqC,CAKxE,OAJI,IAAW,IAAI,EAAY,KAAK,EAAS,GAAG,CAEhD,EAAI,YAAY,EAAY,IAAI,EAAK,MAAM;EAAK,CAAC,OAAO,kBAAkB,EAAW,iBAAiB,EAAY,KAAK,IAAI,CAAC,GAAG,CAExH,CAAE,OAAM,aAAY,cAAa,cAAa"}
@@ -5,19 +5,19 @@ import { existsSync as a } from "node:fs";
5
5
  //#region src/compiler/index.ts
6
6
  var o = n(i(import.meta.url));
7
7
  async function s(n) {
8
- let i = n.logger ?? (() => {}), s = n.projectRoot ?? process.cwd(), c = t(r(s, n.entry)).replace(/\.[jt]sx?$/, ""), l = r(o, "../../scripts/compile-to-piu.ts");
9
- if (!a(l)) throw Error(`Compiler script not found at ${l}`);
10
- i(`Compiling ${c}...`);
11
- let u = {
8
+ let i = n.logger ?? (() => {}), s = n.projectRoot ?? process.cwd(), c = r(s, n.entry), l = t(c).replace(/\.[jt]sx?$/, ""), u = r(o, "../../scripts/compile-to-piu.ts");
9
+ if (!a(u)) throw Error(`Compiler script not found at ${u}`);
10
+ i(`Compiling ${l}...`);
11
+ let d = {
12
12
  ...process.env,
13
13
  EXAMPLE: c
14
14
  };
15
- n.settleMs && (u.SETTLE_MS = String(n.settleMs)), n.platform && (u.PEBBLE_PLATFORM = n.platform);
16
- let d, f;
15
+ n.settleMs && (d.SETTLE_MS = String(n.settleMs)), n.platform && (d.PEBBLE_PLATFORM = n.platform);
16
+ let f, p;
17
17
  try {
18
- d = e(`npx tsx "${l}"`, {
18
+ f = e(`npx tsx "${u}"`, {
19
19
  cwd: s,
20
- env: u,
20
+ env: d,
21
21
  encoding: "utf-8",
22
22
  timeout: 3e4,
23
23
  stdio: [
@@ -27,25 +27,25 @@ async function s(n) {
27
27
  ]
28
28
  });
29
29
  try {
30
- f = e(`npx tsx "${l}" 2>&1 1>/dev/null`, {
30
+ p = e(`npx tsx "${u}" 2>&1 1>/dev/null`, {
31
31
  cwd: s,
32
- env: u,
32
+ env: d,
33
33
  encoding: "utf-8",
34
34
  timeout: 3e4
35
35
  });
36
36
  } catch {
37
- f = "";
37
+ p = "";
38
38
  }
39
39
  } catch (e) {
40
40
  let t = e;
41
- throw Error(`Compilation failed for ${c}: ${t.stderr ?? t.message}`);
41
+ throw Error(`Compilation failed for ${l}: ${t.stderr ?? t.message}`);
42
42
  }
43
- let p = f.includes("Button bindings discovered:") && !f.includes("Button bindings discovered: 0"), m = [], h = f.match(/useMessage detected: key="([^"]+)"/);
44
- return h?.[1] && m.push(h[1]), i(`Compiled ${c}: ${d.split("\n").length} lines, buttons=${p}, messageKeys=[${m.join(",")}]`), {
45
- code: d,
46
- hasButtons: p,
47
- messageKeys: m,
48
- diagnostics: f
43
+ let m = p.includes("Button bindings discovered:") && !p.includes("Button bindings discovered: 0"), h = [], g = p.match(/useMessage detected: key="([^"]+)"/);
44
+ return g?.[1] && h.push(g[1]), i(`Compiled ${l}: ${f.split("\n").length} lines, buttons=${m}, messageKeys=[${h.join(",")}]`), {
45
+ code: f,
46
+ hasButtons: m,
47
+ messageKeys: h,
48
+ diagnostics: p
49
49
  };
50
50
  }
51
51
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.js","names":[],"sources":["../../src/compiler/index.ts"],"sourcesContent":["/**\n * src/compiler/index.ts — react-pebble compile-to-piu library API.\n *\n * Wraps the compile-to-piu.ts script as a programmatic API. The script\n * runs as a subprocess (it uses module-level state that requires process\n * isolation). A future refactoring will inline the logic as a pure function.\n *\n * Usage:\n * import { compileToPiu } from 'react-pebble/compiler';\n * const result = await compileToPiu({ entry: 'examples/watchface.tsx' });\n * console.log(result.code); // piu Application.template JS\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve, dirname, basename } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { existsSync } from 'node:fs';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface CompileOptions {\n /** Path to the entry .tsx file (relative to cwd or absolute) */\n entry: string;\n /** Milliseconds to wait for async effects (useEffect/setTimeout) */\n settleMs?: number;\n /** Target platform (default: 'emery') */\n platform?: string;\n /** Logger for diagnostic messages */\n logger?: (msg: string) => void;\n /** Project root directory (default: process.cwd()) */\n projectRoot?: string;\n}\n\nexport interface CompileResult {\n /** The compiled piu JavaScript code */\n code: string;\n /** Whether the component uses useButton (needs watchapp mode) */\n hasButtons: boolean;\n /** Message keys used by useMessage hooks */\n messageKeys: string[];\n /** Diagnostic messages from the compiler */\n diagnostics: string;\n}\n\n/**\n * Compile a Preact component to piu Application.template code.\n *\n * Internally runs scripts/compile-to-piu.ts as a subprocess.\n */\nexport async function compileToPiu(options: CompileOptions): Promise<CompileResult> {\n const log = options.logger ?? (() => {});\n const projectRoot = options.projectRoot ?? process.cwd();\n\n // Resolve the entry to an example name (for backwards compat with the script)\n const entryPath = resolve(projectRoot, options.entry);\n const exampleName = basename(entryPath).replace(/\\.[jt]sx?$/, '');\n\n // Find the compiler script\n const scriptPath = resolve(__dirname, '../../scripts/compile-to-piu.ts');\n if (!existsSync(scriptPath)) {\n throw new Error(`Compiler script not found at ${scriptPath}`);\n }\n\n log(`Compiling ${exampleName}...`);\n\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n EXAMPLE: exampleName,\n };\n if (options.settleMs) {\n env.SETTLE_MS = String(options.settleMs);\n }\n if (options.platform) {\n env.PEBBLE_PLATFORM = options.platform;\n }\n\n // Run the compiler script and capture stdout (code) + stderr (diagnostics)\n let code: string;\n let diagnostics: string;\n try {\n code = execSync(`npx tsx \"${scriptPath}\"`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Re-run to capture stderr separately (execSync doesn't give both easily)\n try {\n diagnostics = execSync(`npx tsx \"${scriptPath}\" 2>&1 1>/dev/null`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n });\n } catch {\n diagnostics = '';\n }\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw new Error(`Compilation failed for ${exampleName}: ${e.stderr ?? e.message}`);\n }\n\n // Parse diagnostics to extract metadata\n const hasButtons = diagnostics.includes('Button bindings discovered:') &&\n !diagnostics.includes('Button bindings discovered: 0');\n const messageKeys: string[] = [];\n const msgMatch = diagnostics.match(/useMessage detected: key=\"([^\"]+)\"/);\n if (msgMatch?.[1]) messageKeys.push(msgMatch[1]);\n\n log(`Compiled ${exampleName}: ${code.split('\\n').length} lines, buttons=${hasButtons}, messageKeys=[${messageKeys.join(',')}]`);\n\n return { code, hasButtons, messageKeys, diagnostics };\n}\n"],"mappings":";;;;;AAkBA,IAAM,IAAY,EAAQ,EAAc,OAAO,KAAK,IAAI,CAAC;AA+BzD,eAAsB,EAAa,GAAiD;CAClF,IAAM,IAAM,EAAQ,iBAAiB,KAC/B,IAAc,EAAQ,eAAe,QAAQ,KAAK,EAIlD,IAAc,EADF,EAAQ,GAAa,EAAQ,MAAM,CACd,CAAC,QAAQ,cAAc,GAAG,EAG3D,IAAa,EAAQ,GAAW,kCAAkC;AACxE,KAAI,CAAC,EAAW,EAAW,CACzB,OAAU,MAAM,gCAAgC,IAAa;AAG/D,GAAI,aAAa,EAAY,KAAK;CAElC,IAAM,IAA8B;EAClC,GAAG,QAAQ;EACX,SAAS;EACV;AAID,CAHI,EAAQ,aACV,EAAI,YAAY,OAAO,EAAQ,SAAS,GAEtC,EAAQ,aACV,EAAI,kBAAkB,EAAQ;CAIhC,IAAI,GACA;AACJ,KAAI;AACF,MAAO,EAAS,YAAY,EAAW,IAAI;GACzC,KAAK;GACL;GACA,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAQ;IAAQ;IAAO;GAChC,CAAC;AAEF,MAAI;AACF,OAAc,EAAS,YAAY,EAAW,qBAAqB;IACjE,KAAK;IACL;IACA,UAAU;IACV,SAAS;IACV,CAAC;UACI;AACN,OAAc;;UAET,GAAK;EACZ,IAAM,IAAI;AACV,QAAU,MAAM,0BAA0B,EAAY,IAAI,EAAE,UAAU,EAAE,UAAU;;CAIpF,IAAM,IAAa,EAAY,SAAS,8BAA8B,IACpE,CAAC,EAAY,SAAS,gCAAgC,EAClD,IAAwB,EAAE,EAC1B,IAAW,EAAY,MAAM,qCAAqC;AAKxE,QAJI,IAAW,MAAI,EAAY,KAAK,EAAS,GAAG,EAEhD,EAAI,YAAY,EAAY,IAAI,EAAK,MAAM,KAAK,CAAC,OAAO,kBAAkB,EAAW,iBAAiB,EAAY,KAAK,IAAI,CAAC,GAAG,EAExH;EAAE;EAAM;EAAY;EAAa;EAAa"}
1
+ {"version":3,"file":"compiler.js","names":[],"sources":["../../src/compiler/index.ts"],"sourcesContent":["/**\n * src/compiler/index.ts — react-pebble compile-to-piu library API.\n *\n * Wraps the compile-to-piu.ts script as a programmatic API. The script\n * runs as a subprocess (it uses module-level state that requires process\n * isolation). A future refactoring will inline the logic as a pure function.\n *\n * Usage:\n * import { compileToPiu } from 'react-pebble/compiler';\n * const result = await compileToPiu({ entry: 'examples/watchface.tsx' });\n * console.log(result.code); // piu Application.template JS\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve, dirname, basename } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { existsSync } from 'node:fs';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface CompileOptions {\n /** Path to the entry .tsx file (relative to cwd or absolute) */\n entry: string;\n /** Milliseconds to wait for async effects (useEffect/setTimeout) */\n settleMs?: number;\n /** Target platform (default: 'emery') */\n platform?: string;\n /** Logger for diagnostic messages */\n logger?: (msg: string) => void;\n /** Project root directory (default: process.cwd()) */\n projectRoot?: string;\n}\n\nexport interface CompileResult {\n /** The compiled piu JavaScript code */\n code: string;\n /** Whether the component uses useButton (needs watchapp mode) */\n hasButtons: boolean;\n /** Message keys used by useMessage hooks */\n messageKeys: string[];\n /** Diagnostic messages from the compiler */\n diagnostics: string;\n}\n\n/**\n * Compile a Preact component to piu Application.template code.\n *\n * Internally runs scripts/compile-to-piu.ts as a subprocess.\n */\nexport async function compileToPiu(options: CompileOptions): Promise<CompileResult> {\n const log = options.logger ?? (() => {});\n const projectRoot = options.projectRoot ?? process.cwd();\n\n // Resolve the entry path pass the full path so the script can find it\n // whether it's an internal example or an external project file\n const entryPath = resolve(projectRoot, options.entry);\n const exampleName = basename(entryPath).replace(/\\.[jt]sx?$/, '');\n\n // Find the compiler script\n const scriptPath = resolve(__dirname, '../../scripts/compile-to-piu.ts');\n if (!existsSync(scriptPath)) {\n throw new Error(`Compiler script not found at ${scriptPath}`);\n }\n\n log(`Compiling ${exampleName}...`);\n\n const env: Record<string, string> = {\n ...process.env as Record<string, string>,\n EXAMPLE: entryPath,\n };\n if (options.settleMs) {\n env.SETTLE_MS = String(options.settleMs);\n }\n if (options.platform) {\n env.PEBBLE_PLATFORM = options.platform;\n }\n\n // Run the compiler script and capture stdout (code) + stderr (diagnostics)\n let code: string;\n let diagnostics: string;\n try {\n code = execSync(`npx tsx \"${scriptPath}\"`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Re-run to capture stderr separately (execSync doesn't give both easily)\n try {\n diagnostics = execSync(`npx tsx \"${scriptPath}\" 2>&1 1>/dev/null`, {\n cwd: projectRoot,\n env,\n encoding: 'utf-8',\n timeout: 30000,\n });\n } catch {\n diagnostics = '';\n }\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw new Error(`Compilation failed for ${exampleName}: ${e.stderr ?? e.message}`);\n }\n\n // Parse diagnostics to extract metadata\n const hasButtons = diagnostics.includes('Button bindings discovered:') &&\n !diagnostics.includes('Button bindings discovered: 0');\n const messageKeys: string[] = [];\n const msgMatch = diagnostics.match(/useMessage detected: key=\"([^\"]+)\"/);\n if (msgMatch?.[1]) messageKeys.push(msgMatch[1]);\n\n log(`Compiled ${exampleName}: ${code.split('\\n').length} lines, buttons=${hasButtons}, messageKeys=[${messageKeys.join(',')}]`);\n\n return { code, hasButtons, messageKeys, diagnostics };\n}\n"],"mappings":";;;;;AAkBA,IAAM,IAAY,EAAQ,EAAc,OAAO,KAAK,IAAI,CAAC;AA+BzD,eAAsB,EAAa,GAAiD;CAClF,IAAM,IAAM,EAAQ,iBAAiB,KAC/B,IAAc,EAAQ,eAAe,QAAQ,KAAK,EAIlD,IAAY,EAAQ,GAAa,EAAQ,MAAM,EAC/C,IAAc,EAAS,EAAU,CAAC,QAAQ,cAAc,GAAG,EAG3D,IAAa,EAAQ,GAAW,kCAAkC;AACxE,KAAI,CAAC,EAAW,EAAW,CACzB,OAAU,MAAM,gCAAgC,IAAa;AAG/D,GAAI,aAAa,EAAY,KAAK;CAElC,IAAM,IAA8B;EAClC,GAAG,QAAQ;EACX,SAAS;EACV;AAID,CAHI,EAAQ,aACV,EAAI,YAAY,OAAO,EAAQ,SAAS,GAEtC,EAAQ,aACV,EAAI,kBAAkB,EAAQ;CAIhC,IAAI,GACA;AACJ,KAAI;AACF,MAAO,EAAS,YAAY,EAAW,IAAI;GACzC,KAAK;GACL;GACA,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAQ;IAAQ;IAAO;GAChC,CAAC;AAEF,MAAI;AACF,OAAc,EAAS,YAAY,EAAW,qBAAqB;IACjE,KAAK;IACL;IACA,UAAU;IACV,SAAS;IACV,CAAC;UACI;AACN,OAAc;;UAET,GAAK;EACZ,IAAM,IAAI;AACV,QAAU,MAAM,0BAA0B,EAAY,IAAI,EAAE,UAAU,EAAE,UAAU;;CAIpF,IAAM,IAAa,EAAY,SAAS,8BAA8B,IACpE,CAAC,EAAY,SAAS,gCAAgC,EAClD,IAAwB,EAAE,EAC1B,IAAW,EAAY,MAAM,qCAAqC;AAKxE,QAJI,IAAW,MAAI,EAAY,KAAK,EAAS,GAAG,EAEhD,EAAI,YAAY,EAAY,IAAI,EAAK,MAAM,KAAK,CAAC,OAAO,kBAAkB,EAAW,iBAAiB,EAAY,KAAK,IAAI,CAAC,GAAG,EAExH;EAAE;EAAM;EAAY;EAAa;EAAa"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-pebble",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Write Pebble watchfaces and apps in JSX — compiles to piu for Pebble Alloy at build time",
5
5
  "type": "module",
6
6
  "main": "dist/lib/index.cjs",
@@ -36,18 +36,31 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
36
36
  // Dynamic example import
37
37
  // ---------------------------------------------------------------------------
38
38
 
39
- const exampleName = process.env.EXAMPLE ?? 'watchface';
39
+ const exampleInput = process.env.EXAMPLE ?? 'watchface';
40
40
  const settleMs = Number(process.env.SETTLE_MS ?? '0');
41
41
  const platform = process.env.PEBBLE_PLATFORM ?? 'emery';
42
42
  const settle = () =>
43
43
  settleMs > 0 ? new Promise<void>((r) => setTimeout(r, settleMs)) : Promise.resolve();
44
44
 
45
45
  // Set platform screen dimensions before importing the example
46
- // (so SCREEN.width/height are correct when the component renders)
47
46
  import { _setPlatform, SCREEN } from '../src/platform.js';
48
47
  _setPlatform(platform);
49
48
 
50
- const exampleMod = await import(`../examples/${exampleName}.js`);
49
+ // Resolve the entry: could be a bare name (e.g., "watchface") for internal examples,
50
+ // or an absolute/relative path (e.g., "/tmp/my-app/src/App.tsx") for external projects.
51
+ let entryPath: string;
52
+ let exampleName: string;
53
+ if (exampleInput.includes('/') || exampleInput.includes('\\')) {
54
+ // Absolute or relative path — resolve from cwd
55
+ entryPath = resolve(exampleInput);
56
+ exampleName = entryPath.replace(/\.[jt]sx?$/, '').split('/').pop()!;
57
+ } else {
58
+ // Bare name — look in ../examples/
59
+ entryPath = resolve(__dirname, '..', 'examples', `${exampleInput}.tsx`);
60
+ exampleName = exampleInput;
61
+ }
62
+
63
+ const exampleMod = await import(entryPath);
51
64
  const exampleMain: (...args: unknown[]) => ReturnType<typeof render> =
52
65
  exampleMod.main ?? exampleMod.default;
53
66
 
@@ -165,8 +178,17 @@ const buttonBindings: ButtonBinding[] = [];
165
178
  * Parse an example source file into a TypeScript AST SourceFile.
166
179
  */
167
180
  function parseExampleSource(exName: string): ts.SourceFile | null {
168
- for (const ext of ['.tsx', '.ts', '.jsx']) {
169
- const srcPath = resolve(__dirname, '..', 'examples', `${exName}${ext}`);
181
+ // If exName is an absolute path, try it directly
182
+ if (exName.startsWith('/')) {
183
+ try {
184
+ const source = readFileSync(exName, 'utf-8');
185
+ return ts.createSourceFile(exName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
186
+ } catch { /* fall through to extension search */ }
187
+ }
188
+ for (const ext of ['.tsx', '.ts', '.jsx', '']) {
189
+ const srcPath = exName.startsWith('/')
190
+ ? `${exName}${ext}`
191
+ : resolve(__dirname, '..', 'examples', `${exName}${ext}`);
170
192
  try {
171
193
  const source = readFileSync(srcPath, 'utf-8');
172
194
  return ts.createSourceFile(srcPath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
@@ -248,8 +270,8 @@ function buildSetterSlotMap(exName: string): Map<string, number> {
248
270
  return map;
249
271
  }
250
272
 
251
- const setterSlotMap = buildSetterSlotMap(exampleName);
252
- const listInfo = detectListPatterns(exampleName);
273
+ const setterSlotMap = buildSetterSlotMap(entryPath);
274
+ const listInfo = detectListPatterns(entryPath);
253
275
  if (listInfo) {
254
276
  process.stderr.write(`List detected: array="${listInfo.dataArrayName}" visible=${listInfo.visibleCount} labelsPerItem=${listInfo.labelsPerItem}\n`);
255
277
  if (listInfo.dataArrayValues) process.stderr.write(` values: ${JSON.stringify(listInfo.dataArrayValues)}\n`);
@@ -298,7 +320,7 @@ function detectUseMessage(exName: string): MessageInfo | null {
298
320
  return { key, mockDataArrayName };
299
321
  }
300
322
 
301
- const messageInfo = detectUseMessage(exampleName);
323
+ const messageInfo = detectUseMessage(entryPath);
302
324
  if (messageInfo) {
303
325
  process.stderr.write(`useMessage detected: key="${messageInfo.key}"${messageInfo.mockDataArrayName ? ` mockData=${messageInfo.mockDataArrayName}` : ''}\n`);
304
326
  }
@@ -879,7 +901,7 @@ let listScrollSlotIndex = -1;
879
901
 
880
902
  // Install interceptors BEFORE any rendering
881
903
  installUseStateInterceptor();
882
- extractButtonBindingsFromSource(exampleName);
904
+ extractButtonBindingsFromSource(entryPath);
883
905
 
884
906
  // Create BOTH test dates with the REAL Date before any mocking.
885
907
  const OrigDate = globalThis.Date;
@@ -51,7 +51,8 @@ export async function compileToPiu(options: CompileOptions): Promise<CompileResu
51
51
  const log = options.logger ?? (() => {});
52
52
  const projectRoot = options.projectRoot ?? process.cwd();
53
53
 
54
- // Resolve the entry to an example name (for backwards compat with the script)
54
+ // Resolve the entry path pass the full path so the script can find it
55
+ // whether it's an internal example or an external project file
55
56
  const entryPath = resolve(projectRoot, options.entry);
56
57
  const exampleName = basename(entryPath).replace(/\.[jt]sx?$/, '');
57
58
 
@@ -65,7 +66,7 @@ export async function compileToPiu(options: CompileOptions): Promise<CompileResu
65
66
 
66
67
  const env: Record<string, string> = {
67
68
  ...process.env as Record<string, string>,
68
- EXAMPLE: exampleName,
69
+ EXAMPLE: entryPath,
69
70
  };
70
71
  if (options.settleMs) {
71
72
  env.SETTLE_MS = String(options.settleMs);