@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (301) hide show
  1. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  2. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  4. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  5. package/dist/conformance/.generated/can-i-use.js +2 -2
  6. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  7. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  8. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  9. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  10. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  11. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  12. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  13. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  14. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  19. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  20. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  23. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  24. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/index.json +200 -25
  30. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  35. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  36. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  37. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  38. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  39. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  40. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  41. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  42. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  50. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  51. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +131 -66
  57. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
  58. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  68. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  69. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  71. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  72. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  80. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  81. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  99. package/dist/serve/docs-ui/llms.txt +1 -1
  100. package/dist/serve/worker/host/core.js +1 -1
  101. package/dist/serve/worker/host/core.js.map +1 -1
  102. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  103. package/dist/serve/worker/host/firestore-writes.js +3 -2
  104. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  105. package/dist/serve/worker/index.d.ts +2 -1
  106. package/dist/serve/worker/index.d.ts.map +1 -1
  107. package/dist/serve/worker/index.js +2 -1
  108. package/dist/serve/worker/index.js.map +1 -1
  109. package/dist/serve/worker/protocol.js +2 -2
  110. package/dist/serve/worker/protocol.js.map +1 -1
  111. package/package.json +5 -4
  112. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  113. package/src/assurance/attachment.ts +211 -0
  114. package/src/assurance/browser.ts +80 -0
  115. package/src/assurance/campaign.ts +496 -0
  116. package/src/assurance/capabilities.ts +446 -0
  117. package/src/assurance/cases.ts +43 -0
  118. package/src/assurance/index.ts +75 -0
  119. package/src/assurance/runner.ts +885 -0
  120. package/src/assurance/tool-names.ts +15 -0
  121. package/src/assurance/tools.ts +830 -0
  122. package/src/assurance/types.ts +352 -0
  123. package/src/assurance/validation.ts +434 -0
  124. package/src/bridge/client/bridge.ts +550 -0
  125. package/src/bridge/client/dispatch.ts +137 -0
  126. package/src/bridge/client.ts +45 -0
  127. package/src/bridge/protocol.ts +351 -0
  128. package/src/bridge/server/audit.ts +53 -0
  129. package/src/bridge/server/bridge.ts +597 -0
  130. package/src/bridge/server/headless.ts +160 -0
  131. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  132. package/src/bridge/server/local-bridge.ts +66 -0
  133. package/src/bridge/server/logger.ts +50 -0
  134. package/src/bridge/server/mcp-contract.ts +93 -0
  135. package/src/bridge/server/mcp.ts +129 -0
  136. package/src/bridge/server/peer.ts +232 -0
  137. package/src/bridge/server/standalone.ts +347 -0
  138. package/src/bridge/server/tool-metadata.ts +99 -0
  139. package/src/bridge/server.ts +28 -0
  140. package/src/cli/can-i-use.ts +50 -0
  141. package/src/cli/cli.test.ts +493 -0
  142. package/src/cli/database-rules.ts +329 -0
  143. package/src/cli/dev-runner.ts +282 -0
  144. package/src/cli/firebase-json.ts +100 -0
  145. package/src/cli/firestore-indexes.ts +70 -0
  146. package/src/cli/index.ts +425 -0
  147. package/src/cli/init.ts +260 -0
  148. package/src/cli/mcp-proxy.ts +196 -0
  149. package/src/cli/parse-args.ts +80 -0
  150. package/src/cli/rules.ts +259 -0
  151. package/src/cli/scope.ts +67 -0
  152. package/src/cli/serve.ts +996 -0
  153. package/src/cli/service-commands.ts +73 -0
  154. package/src/cli/snapshot.ts +164 -0
  155. package/src/cli/storage-rules.ts +172 -0
  156. package/src/cli/verify.ts +416 -0
  157. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  158. package/src/conformance/.generated/can-i-use.ts +117 -0
  159. package/src/conformance/.generated/conformance-docs.ts +14 -0
  160. package/src/conformance/browser.ts +24 -0
  161. package/src/conformance/can-i-use-tool.ts +51 -0
  162. package/src/conformance/can-i-use.ts +18 -0
  163. package/src/conformance/docs.ts +8 -0
  164. package/src/conformance/index.ts +20 -0
  165. package/src/conformance/tools.ts +16 -0
  166. package/src/credentials/core/memoize-ttl.ts +142 -0
  167. package/src/credentials/core/types.ts +10 -0
  168. package/src/credentials/node/from-adc.ts +96 -0
  169. package/src/credentials/node/from-service-account.ts +133 -0
  170. package/src/credentials/node/index.ts +6 -0
  171. package/src/discover/concurrency.ts +127 -0
  172. package/src/discover/crawler-adapter.ts +142 -0
  173. package/src/discover/crawler.ts +1127 -0
  174. package/src/discover/credential-free.ts +27 -0
  175. package/src/discover/findCollectionGroup.ts +131 -0
  176. package/src/discover/firestore-source.ts +59 -0
  177. package/src/discover/index.ts +5 -0
  178. package/src/discover/merge.ts +523 -0
  179. package/src/discover/session.ts +402 -0
  180. package/src/discover/tools.ts +201 -0
  181. package/src/discover/types.ts +187 -0
  182. package/src/discover/wire.ts +324 -0
  183. package/src/functions-rtdb/child.ts +382 -0
  184. package/src/functions-rtdb/delivery.ts +7 -0
  185. package/src/functions-rtdb/discovery.ts +136 -0
  186. package/src/functions-rtdb/event.ts +64 -0
  187. package/src/functions-rtdb/execution.ts +111 -0
  188. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  189. package/src/functions-rtdb/project.ts +74 -0
  190. package/src/functions-rtdb/projection.ts +104 -0
  191. package/src/functions-rtdb/reference-pattern.ts +20 -0
  192. package/src/functions-rtdb/remote-delivery.ts +23 -0
  193. package/src/pkg-version.ts +49 -0
  194. package/src/register/esm-exports.ts +63 -0
  195. package/src/register/hooks.ts +30 -0
  196. package/src/register/index.ts +150 -0
  197. package/src/register/mapping.ts +33 -0
  198. package/src/remote/index.ts +1050 -0
  199. package/src/rtdb/crawl-snapshot.ts +118 -0
  200. package/src/rtdb/inspection.ts +137 -0
  201. package/src/rtdb/load-rules-document.ts +56 -0
  202. package/src/rtdb/rules-generation-tool.ts +38 -0
  203. package/src/rtdb/rules-json.ts +29 -0
  204. package/src/serve/activity-guard.ts +29 -0
  205. package/src/serve/activity-route.ts +164 -0
  206. package/src/serve/activity-warning.ts +26 -0
  207. package/src/serve/bridge-mount.ts +200 -0
  208. package/src/serve/bundler.ts +493 -0
  209. package/src/serve/capture-store.ts +65 -0
  210. package/src/serve/discovery.ts +199 -0
  211. package/src/serve/entries/ai.ts +292 -0
  212. package/src/serve/entries/app-backend.ts +4 -0
  213. package/src/serve/entries/app-client.ts +26 -0
  214. package/src/serve/entries/app-session-store.ts +47 -0
  215. package/src/serve/entries/app.ts +11 -0
  216. package/src/serve/entries/auth-helper-core.ts +181 -0
  217. package/src/serve/entries/auth-helper-dom.ts +134 -0
  218. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  219. package/src/serve/entries/auth.ts +255 -0
  220. package/src/serve/entries/bridge-url.ts +31 -0
  221. package/src/serve/entries/database.ts +129 -0
  222. package/src/serve/entries/firestore.ts +232 -0
  223. package/src/serve/entries/init.ts +46 -0
  224. package/src/serve/entries/keepalive.ts +46 -0
  225. package/src/serve/entries/messaging-sw.ts +75 -0
  226. package/src/serve/entries/messaging.ts +96 -0
  227. package/src/serve/entries/runtime.ts +540 -0
  228. package/src/serve/entries/session-store.ts +97 -0
  229. package/src/serve/entries/storage.ts +92 -0
  230. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  231. package/src/serve/entries/worker-runtime.ts +75 -0
  232. package/src/serve/init-payload.ts +42 -0
  233. package/src/serve/namespace.ts +675 -0
  234. package/src/serve/open-browser.ts +68 -0
  235. package/src/serve/rules.ts +272 -0
  236. package/src/serve/sandbox-marker.ts +31 -0
  237. package/src/serve/server.ts +428 -0
  238. package/src/serve/standalone-assets.ts +180 -0
  239. package/src/serve/state-store.ts +166 -0
  240. package/src/serve/studio/disk-project-store.ts +185 -0
  241. package/src/serve/studio/disk-workspace.ts +162 -0
  242. package/src/serve/studio/index.ts +20 -0
  243. package/src/serve/studio/routes.ts +237 -0
  244. package/src/serve/studio/store-types.ts +48 -0
  245. package/src/serve/studio/studio-storage.test.ts +289 -0
  246. package/src/serve/vite-plugin.ts +1102 -0
  247. package/src/serve/worker/activity-bootstrap.ts +24 -0
  248. package/src/serve/worker/client/admin-firestore.ts +38 -0
  249. package/src/serve/worker/client/ai.ts +124 -0
  250. package/src/serve/worker/client/auth.ts +489 -0
  251. package/src/serve/worker/client/connection.ts +202 -0
  252. package/src/serve/worker/client/core.ts +350 -0
  253. package/src/serve/worker/client/disconnect.ts +45 -0
  254. package/src/serve/worker/client/firestore-reads.ts +181 -0
  255. package/src/serve/worker/client/firestore-refs.ts +226 -0
  256. package/src/serve/worker/client/firestore-writes.ts +232 -0
  257. package/src/serve/worker/client/handles.ts +91 -0
  258. package/src/serve/worker/client/messaging.ts +119 -0
  259. package/src/serve/worker/client/presence.ts +174 -0
  260. package/src/serve/worker/client/rtdb.ts +311 -0
  261. package/src/serve/worker/client/rules.ts +63 -0
  262. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  263. package/src/serve/worker/client/snapshots.ts +86 -0
  264. package/src/serve/worker/client/storage.ts +195 -0
  265. package/src/serve/worker/client/studio.ts +88 -0
  266. package/src/serve/worker/client.ts +57 -0
  267. package/src/serve/worker/durable-persistence.ts +137 -0
  268. package/src/serve/worker/entry.ts +179 -0
  269. package/src/serve/worker/host/admin-firestore.ts +88 -0
  270. package/src/serve/worker/host/connection.ts +179 -0
  271. package/src/serve/worker/host/core.ts +387 -0
  272. package/src/serve/worker/host/dispatch.ts +316 -0
  273. package/src/serve/worker/host/firestore-reads.ts +117 -0
  274. package/src/serve/worker/host/firestore-writes.ts +456 -0
  275. package/src/serve/worker/host/presence.ts +312 -0
  276. package/src/serve/worker/host/rtdb.ts +136 -0
  277. package/src/serve/worker/host/rules.ts +128 -0
  278. package/src/serve/worker/host/storage.ts +312 -0
  279. package/src/serve/worker/host/studio.ts +75 -0
  280. package/src/serve/worker/host/subscriptions.ts +238 -0
  281. package/src/serve/worker/host-ai.ts +165 -0
  282. package/src/serve/worker/host-auth.ts +469 -0
  283. package/src/serve/worker/host-context.ts +273 -0
  284. package/src/serve/worker/host-events.ts +94 -0
  285. package/src/serve/worker/host-messaging.ts +240 -0
  286. package/src/serve/worker/host.ts +47 -0
  287. package/src/serve/worker/index.ts +196 -0
  288. package/src/serve/worker/presence-timing.ts +15 -0
  289. package/src/serve/worker/protocol.ts +1112 -0
  290. package/src/serve/worker/serve-init.ts +606 -0
  291. package/src/serve/worker/service-worker-channel.ts +33 -0
  292. package/src/serve/worker/service-worker-relay.ts +86 -0
  293. package/src/serve/writer-lock.ts +53 -0
  294. package/src/verify/cases.ts +233 -0
  295. package/src/verify/fixture.ts +258 -0
  296. package/src/verify/index.ts +519 -0
  297. package/src/verify/tools.ts +108 -0
  298. package/src/version/compat-target.ts +17 -0
  299. package/src/vite.ts +17 -0
  300. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  301. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Best-effort browser auto-open for `pyric dev`.
3
+ *
4
+ * The pyric sandbox is browser-resident: firestore/auth/persistence all run
5
+ * IN the served page, so a dev who never opens it sees data ops silently
6
+ * no-op. Opening the page on start (and the loud banner warning that pairs
7
+ * with it) removes that surprise. Opening is ALWAYS best-effort — a failed
8
+ * open must never fail `dev`; the URL is in the banner regardless.
9
+ *
10
+ * Auto-open is suppressed in the non-interactive paths where popping a
11
+ * browser is wrong or useless: `--json` (the agent/CI stdout contract),
12
+ * `--no-open`, no TTY (piped/redirected), and CI. `--no-open` is the manual
13
+ * escape hatch for an interactive shell that still doesn't want it.
14
+ */
15
+ import { execFile } from 'node:child_process';
16
+ import { platform } from 'node:os';
17
+
18
+ export interface AutoOpenContext {
19
+ /** `--json` is on (stdout is the agent contract — never pop a browser). */
20
+ json: boolean;
21
+ /** `--no-open` was passed. */
22
+ noOpen: boolean;
23
+ /** stdout is a TTY (interactive). Piped/redirected → false. */
24
+ isTTY: boolean;
25
+ /** process.env, read for CI detection. */
26
+ env: Record<string, string | undefined>;
27
+ }
28
+
29
+ /**
30
+ * Pure decision: should `pyric dev` auto-open the browser? Factored out so
31
+ * the gating logic is unit-testable without spawning anything. Open only when
32
+ * interactive AND not explicitly suppressed AND not in CI.
33
+ */
34
+ export function shouldAutoOpen(ctx: AutoOpenContext): boolean {
35
+ if (ctx.json || ctx.noOpen || !ctx.isTTY) return false;
36
+ if (ctx.env.CI) return false; // common CI marker; covers GH Actions et al.
37
+ return true;
38
+ }
39
+
40
+ /** The platform command + leading args that open a URL in the default browser. */
41
+ function openCommand(url: string): { cmd: string; args: string[] } {
42
+ switch (platform()) {
43
+ case 'darwin':
44
+ return { cmd: 'open', args: [url] };
45
+ case 'win32':
46
+ // `start` is a cmd builtin; the empty "" is the (ignored) window title
47
+ // so a URL with `&` isn't mis-parsed as the title.
48
+ return { cmd: 'cmd', args: ['/c', 'start', '""', url] };
49
+ default:
50
+ return { cmd: 'xdg-open', args: [url] };
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Open `url` in the default browser, best-effort. Resolves whether or not the
56
+ * open succeeded — callers must not let a missing `xdg-open` (headless Linux,
57
+ * containers) take down the dev server. Never throws.
58
+ */
59
+ export function openBrowser(url: string): Promise<void> {
60
+ return new Promise((resolve) => {
61
+ const { cmd, args } = openCommand(url);
62
+ try {
63
+ execFile(cmd, args, () => resolve()); // ignore exit code/stderr
64
+ } catch {
65
+ resolve(); // spawn itself threw (cmd missing) — swallow
66
+ }
67
+ });
68
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * `pyric dev` rules wiring — load the project's `firestore.rules`, make it
3
+ * executable for the in-page sandbox, and fail FAST at startup on broken
4
+ * rules (a clear CLI error beats a silently rule-less page).
5
+ *
6
+ * `2+modules` sources are resolved node-side (`resolveModulesBrowser` — pure,
7
+ * stdlib inlined, no disk reads) before embedding, so the page runtime only
8
+ * ever sees plain-v2 source the in-browser evaluator understands. This is the
9
+ * same lesson the playground's write_file learned (a capable model authored
10
+ * correct modular rules that scored 0/5 unresolved — auth-sdk work, PR #525).
11
+ */
12
+ import { createHash } from 'node:crypto';
13
+ import { watch, type FSWatcher } from 'node:fs';
14
+ import { readFile } from 'node:fs/promises';
15
+ import { isAbsolute, join } from 'node:path';
16
+ import { lintFirestoreRules, resolveModulesBrowser } from 'pyric/rules/internal';
17
+ import { parseStorageRules } from 'pyric/storage';
18
+ import type { FirebaseJson } from '../cli/firebase-json.js';
19
+ import { parseRtdbRulesJson } from '../rtdb/rules-json.js';
20
+
21
+ export interface LoadedRules {
22
+ /** Plain-v2 source ready for the sandbox, or null when the project has no
23
+ * rules file configured/present. */
24
+ rules: string | null;
25
+ rulesHash: string | null;
26
+ /** Where it came from (diagnostics + the P3 watcher target). */
27
+ sourcePath: string | null;
28
+ }
29
+
30
+ export interface LoadedStorageRules {
31
+ /** Plain storage-rules source ready for the sandbox, or null when the
32
+ * project has no storage rules file configured/present. */
33
+ rules: string | null;
34
+ rulesHash: string | null;
35
+ sourcePath: string | null;
36
+ }
37
+
38
+ export interface LoadedDatabaseRules {
39
+ rules: { rules: Record<string, unknown> } | null;
40
+ rulesHash: string | null;
41
+ sourcePath: string | null;
42
+ databaseUrl: string | null;
43
+ }
44
+
45
+ export function rulesHashOf(source: string): string {
46
+ return createHash('sha256').update(source).digest('hex').slice(0, 12);
47
+ }
48
+
49
+ /**
50
+ * Resolve + lint a raw rules source into sandbox-ready plain v2.
51
+ * Throws with an actionable message on unresolvable imports or lint errors.
52
+ */
53
+ export function prepareRulesSource(raw: string, sourcePath: string): string {
54
+ let source = raw;
55
+ if (/^\s*rules_version\s*=\s*['"]2\+modules['"]/m.test(raw)) {
56
+ const resolved = resolveModulesBrowser(raw);
57
+ if (!resolved.success) {
58
+ throw new Error(
59
+ `pyric dev: ${sourcePath} uses 2+modules but module resolution failed: ${resolved.error.message}`,
60
+ );
61
+ }
62
+ source = resolved.data.resolved;
63
+ }
64
+ const lint = lintFirestoreRules(source);
65
+ if (lint.parseError) {
66
+ const { line, column } = lint.parseError;
67
+ throw new Error(
68
+ `pyric dev: ${sourcePath} failed to parse (line ${line}, col ${column}) — fix the rules before serving.`,
69
+ );
70
+ }
71
+ const errors = lint.warnings.filter((w) => w.severity === 'error');
72
+ if (errors.length > 0) {
73
+ throw new Error(
74
+ `pyric dev: ${sourcePath} has ${errors.length} rules error(s):\n` +
75
+ errors.map((e) => ` - ${e.message}`).join('\n'),
76
+ );
77
+ }
78
+ return source;
79
+ }
80
+
81
+ /**
82
+ * Load the project rules per `firebase.json` (`firestore.rules` path,
83
+ * defaulting to `firestore.rules` in cwd when the key is absent but the file
84
+ * exists). Missing file with no explicit config → `{ rules: null }` (serving
85
+ * without rules is fine; the runtime logs it). Missing file that IS
86
+ * explicitly configured → throw (the project says it should exist).
87
+ */
88
+ export async function loadProjectRules(
89
+ cwd: string,
90
+ config: FirebaseJson | null,
91
+ ): Promise<LoadedRules> {
92
+ const configured = config?.firestore?.rules;
93
+ const rel = configured ?? 'firestore.rules';
94
+ const path = isAbsolute(rel) ? rel : join(cwd, rel);
95
+ let raw: string;
96
+ try {
97
+ raw = await readFile(path, 'utf8');
98
+ } catch (e) {
99
+ if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
100
+ if (configured) {
101
+ throw new Error(`pyric dev: firebase.json points firestore.rules at ${path}, but it does not exist.`);
102
+ }
103
+ return { rules: null, rulesHash: null, sourcePath: null };
104
+ }
105
+ throw e;
106
+ }
107
+ const rules = prepareRulesSource(raw, path);
108
+ return { rules, rulesHash: rulesHashOf(rules), sourcePath: path };
109
+ }
110
+
111
+ /**
112
+ * Validate a raw storage-rules source. `pyric/storage`'s parser throws a
113
+ * plain `SyntaxError` (no line/col like the firestore lint path) — wrap it
114
+ * into the same actionable "fix before serving" message shape.
115
+ */
116
+ export function prepareStorageRulesSource(raw: string, sourcePath: string): string {
117
+ try {
118
+ parseStorageRules(raw);
119
+ } catch (e) {
120
+ throw new Error(
121
+ `pyric dev: ${sourcePath} failed to parse: ${e instanceof Error ? e.message : String(e)} — fix the rules before serving.`,
122
+ );
123
+ }
124
+ return raw;
125
+ }
126
+
127
+ /**
128
+ * Load the project's storage rules per `firebase.json` (`storage.rules`
129
+ * path — the block may be a single object or an array of per-bucket
130
+ * entries; v1 has one implicit bucket, so the FIRST entry with a `rules`
131
+ * path wins, defaulting to `storage.rules` in cwd when the block is absent
132
+ * but the file exists). Missing file with no explicit config → `{ rules:
133
+ * null }` (serving without storage rules is fine — same posture as
134
+ * `loadProjectRules`). Missing file that IS explicitly configured → throw
135
+ * (the project says it should exist).
136
+ */
137
+ export async function loadProjectStorageRules(
138
+ cwd: string,
139
+ config: FirebaseJson | null,
140
+ ): Promise<LoadedStorageRules> {
141
+ const block = config?.storage;
142
+ const entries = block ? (Array.isArray(block) ? block : [block]) : [];
143
+ const configured = entries.find((e) => e && typeof e === 'object' && e.rules)?.rules;
144
+ const rel = configured ?? 'storage.rules';
145
+ const path = isAbsolute(rel) ? rel : join(cwd, rel);
146
+ let raw: string;
147
+ try {
148
+ raw = await readFile(path, 'utf8');
149
+ } catch (e) {
150
+ if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
151
+ if (configured) {
152
+ throw new Error(`pyric dev: firebase.json points storage.rules at ${path}, but it does not exist.`);
153
+ }
154
+ return { rules: null, rulesHash: null, sourcePath: null };
155
+ }
156
+ throw e;
157
+ }
158
+ const rules = prepareStorageRulesSource(raw, path);
159
+ return { rules, rulesHash: rulesHashOf(rules), sourcePath: path };
160
+ }
161
+
162
+ export async function loadProjectDatabaseRules(
163
+ cwd: string,
164
+ config: FirebaseJson | null,
165
+ ): Promise<LoadedDatabaseRules> {
166
+ const configured = config?.database?.rules;
167
+ if (!configured) {
168
+ return {
169
+ rules: null,
170
+ rulesHash: null,
171
+ sourcePath: null,
172
+ databaseUrl: config?.database?.url ?? null,
173
+ };
174
+ }
175
+
176
+ const path = isAbsolute(configured) ? configured : join(cwd, configured);
177
+ let raw: string;
178
+ try {
179
+ raw = await readFile(path, 'utf8');
180
+ } catch (e) {
181
+ if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
182
+ throw new Error(`pyric dev: firebase.json points database.rules at ${path}, but it does not exist.`);
183
+ }
184
+ throw e;
185
+ }
186
+
187
+ let parsed: unknown;
188
+ try {
189
+ parsed = JSON.parse(raw);
190
+ } catch (e) {
191
+ throw new Error(`pyric dev: ${path} failed to parse as RTDB rules JSON: ${e instanceof Error ? e.message : String(e)}`);
192
+ }
193
+ const rules = parseRtdbRulesJson(
194
+ parsed,
195
+ () => new Error(`pyric dev: ${path} must contain a top-level "rules" object.`),
196
+ );
197
+
198
+ return {
199
+ rules,
200
+ rulesHash: rulesHashOf(raw),
201
+ sourcePath: path,
202
+ databaseUrl: config?.database?.url ?? null,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Watch the rules file and invoke `onChange` with freshly prepared
208
+ * (resolved + linted) source. Broken intermediate saves are LOGGED and
209
+ * skipped — the last-good ruleset stays live (the write_file lesson:
210
+ * never replace a working ruleset with un-evaluatable source).
211
+ * Debounced; returns the watcher for shutdown.
212
+ */
213
+ export function watchProjectRules(
214
+ sourcePath: string,
215
+ onChange: (next: { rules: string; rulesHash: string }) => void,
216
+ onError: (message: string) => void,
217
+ debounceMs = 150,
218
+ ): FSWatcher {
219
+ return watchRulesFile(sourcePath, prepareRulesSource, onChange, onError, debounceMs);
220
+ }
221
+
222
+ /**
223
+ * Watch a project's storage.rules file, same contract as
224
+ * {@link watchProjectRules} but validating via `pyric/storage`'s parser.
225
+ */
226
+ export function watchProjectStorageRules(
227
+ sourcePath: string,
228
+ onChange: (next: { rules: string; rulesHash: string }) => void,
229
+ onError: (message: string) => void,
230
+ debounceMs = 150,
231
+ ): FSWatcher {
232
+ return watchRulesFile(sourcePath, prepareStorageRulesSource, onChange, onError, debounceMs);
233
+ }
234
+
235
+ /**
236
+ * Shared watch implementation. Broken intermediate saves are LOGGED and
237
+ * skipped — the last-good ruleset stays live (the write_file lesson: never
238
+ * replace a working ruleset with un-evaluatable source). Debounced; returns
239
+ * the watcher for shutdown.
240
+ */
241
+ function watchRulesFile(
242
+ sourcePath: string,
243
+ prepare: (raw: string, sourcePath: string) => string,
244
+ onChange: (next: { rules: string; rulesHash: string }) => void,
245
+ onError: (message: string) => void,
246
+ debounceMs: number,
247
+ ): FSWatcher {
248
+ let timer: ReturnType<typeof setTimeout> | null = null;
249
+ const watcher = watch(sourcePath, () => {
250
+ if (timer) clearTimeout(timer);
251
+ timer = setTimeout(() => {
252
+ void readFile(sourcePath, 'utf8').then(
253
+ (raw) => {
254
+ try {
255
+ const rules = prepare(raw, sourcePath);
256
+ onChange({ rules, rulesHash: rulesHashOf(rules) });
257
+ } catch (e) {
258
+ onError(e instanceof Error ? e.message : String(e));
259
+ }
260
+ },
261
+ (e) => onError(e instanceof Error ? e.message : String(e)),
262
+ );
263
+ }, debounceMs);
264
+ });
265
+ // An FSWatcher with no 'error' listener throws at the event-loop level on
266
+ // watcher failure (EMFILE, the watched file renamed away on some platforms)
267
+ // and would kill the serve process. Degrade to "no hot reload" instead.
268
+ watcher.on('error', (e) =>
269
+ onError(`rules watcher failed (hot reload off): ${e instanceof Error ? e.message : String(e)}`),
270
+ );
271
+ return watcher;
272
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The sandbox-build marker — the single source of truth shared by the three
3
+ * places that produce or read it: the Vite plugin stamps it into a
4
+ * `vite build --mode pyric` output's `index.html`; `pyric dev` trusts it (a
5
+ * marked dist bundles pyric's in-page adapters, so its assets never inline the
6
+ * real SDK and the inlined-firebase scanner is skipped).
7
+ *
8
+ * Dependency-free (fs/path only) so the dev server can read it without pulling
9
+ * in the browser bundler.
10
+ */
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+
14
+ /** The attribute stamped into a sandbox build's `index.html` head. Presence in
15
+ * an asset's text is the signal — the value is fixed, never parsed. */
16
+ export const SANDBOX_BUILD_MARKER = 'data-pyric-sandbox-build';
17
+
18
+ /** The full `<meta>` tag the plugin injects at the start of `<head>`. */
19
+ export const SANDBOX_BUILD_META = `<meta name="pyric-sandbox-build" content="1" ${SANDBOX_BUILD_MARKER}>`;
20
+
21
+ /** True when `dir`'s `index.html` carries the sandbox-build marker. Missing or
22
+ * unreadable index → false (treated as an ordinary build). */
23
+ export function hasSandboxBuildMarker(dir: string): boolean {
24
+ const index = join(dir, 'index.html');
25
+ if (!existsSync(index)) return false;
26
+ try {
27
+ return readFileSync(index, 'utf8').includes(SANDBOX_BUILD_MARKER);
28
+ } catch {
29
+ return false;
30
+ }
31
+ }