@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.
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +2 -2
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
- package/dist/serve/docs-ui/docs/api-reference.md +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
- package/dist/serve/docs-ui/docs/index.json +200 -25
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +131 -66
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/llms.txt +1 -1
- package/dist/serve/worker/host/core.js +1 -1
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
- package/dist/serve/worker/host/firestore-writes.js +3 -2
- package/dist/serve/worker/host/firestore-writes.js.map +1 -1
- package/dist/serve/worker/index.d.ts +2 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -1
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/protocol.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/package.json +5 -4
- package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
- package/src/assurance/attachment.ts +211 -0
- package/src/assurance/browser.ts +80 -0
- package/src/assurance/campaign.ts +496 -0
- package/src/assurance/capabilities.ts +446 -0
- package/src/assurance/cases.ts +43 -0
- package/src/assurance/index.ts +75 -0
- package/src/assurance/runner.ts +885 -0
- package/src/assurance/tool-names.ts +15 -0
- package/src/assurance/tools.ts +830 -0
- package/src/assurance/types.ts +352 -0
- package/src/assurance/validation.ts +434 -0
- package/src/bridge/client/bridge.ts +550 -0
- package/src/bridge/client/dispatch.ts +137 -0
- package/src/bridge/client.ts +45 -0
- package/src/bridge/protocol.ts +351 -0
- package/src/bridge/server/audit.ts +53 -0
- package/src/bridge/server/bridge.ts +597 -0
- package/src/bridge/server/headless.ts +160 -0
- package/src/bridge/server/json-schema-to-zod.ts +108 -0
- package/src/bridge/server/local-bridge.ts +66 -0
- package/src/bridge/server/logger.ts +50 -0
- package/src/bridge/server/mcp-contract.ts +93 -0
- package/src/bridge/server/mcp.ts +129 -0
- package/src/bridge/server/peer.ts +232 -0
- package/src/bridge/server/standalone.ts +347 -0
- package/src/bridge/server/tool-metadata.ts +99 -0
- package/src/bridge/server.ts +28 -0
- package/src/cli/can-i-use.ts +50 -0
- package/src/cli/cli.test.ts +493 -0
- package/src/cli/database-rules.ts +329 -0
- package/src/cli/dev-runner.ts +282 -0
- package/src/cli/firebase-json.ts +100 -0
- package/src/cli/firestore-indexes.ts +70 -0
- package/src/cli/index.ts +425 -0
- package/src/cli/init.ts +260 -0
- package/src/cli/mcp-proxy.ts +196 -0
- package/src/cli/parse-args.ts +80 -0
- package/src/cli/rules.ts +259 -0
- package/src/cli/scope.ts +67 -0
- package/src/cli/serve.ts +996 -0
- package/src/cli/service-commands.ts +73 -0
- package/src/cli/snapshot.ts +164 -0
- package/src/cli/storage-rules.ts +172 -0
- package/src/cli/verify.ts +416 -0
- package/src/conformance/.generated/can-i-use-browser.ts +113 -0
- package/src/conformance/.generated/can-i-use.ts +117 -0
- package/src/conformance/.generated/conformance-docs.ts +14 -0
- package/src/conformance/browser.ts +24 -0
- package/src/conformance/can-i-use-tool.ts +51 -0
- package/src/conformance/can-i-use.ts +18 -0
- package/src/conformance/docs.ts +8 -0
- package/src/conformance/index.ts +20 -0
- package/src/conformance/tools.ts +16 -0
- package/src/credentials/core/memoize-ttl.ts +142 -0
- package/src/credentials/core/types.ts +10 -0
- package/src/credentials/node/from-adc.ts +96 -0
- package/src/credentials/node/from-service-account.ts +133 -0
- package/src/credentials/node/index.ts +6 -0
- package/src/discover/concurrency.ts +127 -0
- package/src/discover/crawler-adapter.ts +142 -0
- package/src/discover/crawler.ts +1127 -0
- package/src/discover/credential-free.ts +27 -0
- package/src/discover/findCollectionGroup.ts +131 -0
- package/src/discover/firestore-source.ts +59 -0
- package/src/discover/index.ts +5 -0
- package/src/discover/merge.ts +523 -0
- package/src/discover/session.ts +402 -0
- package/src/discover/tools.ts +201 -0
- package/src/discover/types.ts +187 -0
- package/src/discover/wire.ts +324 -0
- package/src/functions-rtdb/child.ts +382 -0
- package/src/functions-rtdb/delivery.ts +7 -0
- package/src/functions-rtdb/discovery.ts +136 -0
- package/src/functions-rtdb/event.ts +64 -0
- package/src/functions-rtdb/execution.ts +111 -0
- package/src/functions-rtdb/in-memory-delivery.ts +40 -0
- package/src/functions-rtdb/project.ts +74 -0
- package/src/functions-rtdb/projection.ts +104 -0
- package/src/functions-rtdb/reference-pattern.ts +20 -0
- package/src/functions-rtdb/remote-delivery.ts +23 -0
- package/src/pkg-version.ts +49 -0
- package/src/register/esm-exports.ts +63 -0
- package/src/register/hooks.ts +30 -0
- package/src/register/index.ts +150 -0
- package/src/register/mapping.ts +33 -0
- package/src/remote/index.ts +1050 -0
- package/src/rtdb/crawl-snapshot.ts +118 -0
- package/src/rtdb/inspection.ts +137 -0
- package/src/rtdb/load-rules-document.ts +56 -0
- package/src/rtdb/rules-generation-tool.ts +38 -0
- package/src/rtdb/rules-json.ts +29 -0
- package/src/serve/activity-guard.ts +29 -0
- package/src/serve/activity-route.ts +164 -0
- package/src/serve/activity-warning.ts +26 -0
- package/src/serve/bridge-mount.ts +200 -0
- package/src/serve/bundler.ts +493 -0
- package/src/serve/capture-store.ts +65 -0
- package/src/serve/discovery.ts +199 -0
- package/src/serve/entries/ai.ts +292 -0
- package/src/serve/entries/app-backend.ts +4 -0
- package/src/serve/entries/app-client.ts +26 -0
- package/src/serve/entries/app-session-store.ts +47 -0
- package/src/serve/entries/app.ts +11 -0
- package/src/serve/entries/auth-helper-core.ts +181 -0
- package/src/serve/entries/auth-helper-dom.ts +134 -0
- package/src/serve/entries/auth-helper-runtime.ts +20 -0
- package/src/serve/entries/auth.ts +255 -0
- package/src/serve/entries/bridge-url.ts +31 -0
- package/src/serve/entries/database.ts +129 -0
- package/src/serve/entries/firestore.ts +232 -0
- package/src/serve/entries/init.ts +46 -0
- package/src/serve/entries/keepalive.ts +46 -0
- package/src/serve/entries/messaging-sw.ts +75 -0
- package/src/serve/entries/messaging.ts +96 -0
- package/src/serve/entries/runtime.ts +540 -0
- package/src/serve/entries/session-store.ts +97 -0
- package/src/serve/entries/storage.ts +92 -0
- package/src/serve/entries/tab-sync-wiring.ts +274 -0
- package/src/serve/entries/worker-runtime.ts +75 -0
- package/src/serve/init-payload.ts +42 -0
- package/src/serve/namespace.ts +675 -0
- package/src/serve/open-browser.ts +68 -0
- package/src/serve/rules.ts +272 -0
- package/src/serve/sandbox-marker.ts +31 -0
- package/src/serve/server.ts +428 -0
- package/src/serve/standalone-assets.ts +180 -0
- package/src/serve/state-store.ts +166 -0
- package/src/serve/studio/disk-project-store.ts +185 -0
- package/src/serve/studio/disk-workspace.ts +162 -0
- package/src/serve/studio/index.ts +20 -0
- package/src/serve/studio/routes.ts +237 -0
- package/src/serve/studio/store-types.ts +48 -0
- package/src/serve/studio/studio-storage.test.ts +289 -0
- package/src/serve/vite-plugin.ts +1102 -0
- package/src/serve/worker/activity-bootstrap.ts +24 -0
- package/src/serve/worker/client/admin-firestore.ts +38 -0
- package/src/serve/worker/client/ai.ts +124 -0
- package/src/serve/worker/client/auth.ts +489 -0
- package/src/serve/worker/client/connection.ts +202 -0
- package/src/serve/worker/client/core.ts +350 -0
- package/src/serve/worker/client/disconnect.ts +45 -0
- package/src/serve/worker/client/firestore-reads.ts +181 -0
- package/src/serve/worker/client/firestore-refs.ts +226 -0
- package/src/serve/worker/client/firestore-writes.ts +232 -0
- package/src/serve/worker/client/handles.ts +91 -0
- package/src/serve/worker/client/messaging.ts +119 -0
- package/src/serve/worker/client/presence.ts +174 -0
- package/src/serve/worker/client/rtdb.ts +311 -0
- package/src/serve/worker/client/rules.ts +63 -0
- package/src/serve/worker/client/service-worker-connection.ts +59 -0
- package/src/serve/worker/client/snapshots.ts +86 -0
- package/src/serve/worker/client/storage.ts +195 -0
- package/src/serve/worker/client/studio.ts +88 -0
- package/src/serve/worker/client.ts +57 -0
- package/src/serve/worker/durable-persistence.ts +137 -0
- package/src/serve/worker/entry.ts +179 -0
- package/src/serve/worker/host/admin-firestore.ts +88 -0
- package/src/serve/worker/host/connection.ts +179 -0
- package/src/serve/worker/host/core.ts +387 -0
- package/src/serve/worker/host/dispatch.ts +316 -0
- package/src/serve/worker/host/firestore-reads.ts +117 -0
- package/src/serve/worker/host/firestore-writes.ts +456 -0
- package/src/serve/worker/host/presence.ts +312 -0
- package/src/serve/worker/host/rtdb.ts +136 -0
- package/src/serve/worker/host/rules.ts +128 -0
- package/src/serve/worker/host/storage.ts +312 -0
- package/src/serve/worker/host/studio.ts +75 -0
- package/src/serve/worker/host/subscriptions.ts +238 -0
- package/src/serve/worker/host-ai.ts +165 -0
- package/src/serve/worker/host-auth.ts +469 -0
- package/src/serve/worker/host-context.ts +273 -0
- package/src/serve/worker/host-events.ts +94 -0
- package/src/serve/worker/host-messaging.ts +240 -0
- package/src/serve/worker/host.ts +47 -0
- package/src/serve/worker/index.ts +196 -0
- package/src/serve/worker/presence-timing.ts +15 -0
- package/src/serve/worker/protocol.ts +1112 -0
- package/src/serve/worker/serve-init.ts +606 -0
- package/src/serve/worker/service-worker-channel.ts +33 -0
- package/src/serve/worker/service-worker-relay.ts +86 -0
- package/src/serve/writer-lock.ts +53 -0
- package/src/verify/cases.ts +233 -0
- package/src/verify/fixture.ts +258 -0
- package/src/verify/index.ts +519 -0
- package/src/verify/tools.ts +108 -0
- package/src/version/compat-target.ts +17 -0
- package/src/vite.ts +17 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join, resolve } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
deriveRulesTestCases,
|
|
5
|
+
fixtureVerifiableServices,
|
|
6
|
+
parseVerifyFixture,
|
|
7
|
+
verifyFixture,
|
|
8
|
+
VerifyInputError,
|
|
9
|
+
type PyricVerifyFixture,
|
|
10
|
+
type VerifyEngine,
|
|
11
|
+
type VerifiableService,
|
|
12
|
+
type VerifyDivergence,
|
|
13
|
+
type VerifyFixtureOptions,
|
|
14
|
+
type VerifyResult,
|
|
15
|
+
type VerifyRulesInput,
|
|
16
|
+
type VerifyServiceResult,
|
|
17
|
+
} from '../verify/index.js';
|
|
18
|
+
import { readFirebaseJson, type FirebaseJson } from './firebase-json.js';
|
|
19
|
+
import type { FlagValue, ParsedArgs } from './parse-args.js';
|
|
20
|
+
import { resolveScope } from './scope.js';
|
|
21
|
+
import { parseRtdbRulesJson } from '../rtdb/rules-json.js';
|
|
22
|
+
|
|
23
|
+
export type Fixture = PyricVerifyFixture;
|
|
24
|
+
|
|
25
|
+
export interface FixtureResult {
|
|
26
|
+
name: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
ok: boolean;
|
|
29
|
+
result: VerifyResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VerifyCliDeps {
|
|
33
|
+
resolveScope?: typeof resolveScope;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const SERVE_CAPTURE_PATH = '.pyric/last-session.json';
|
|
37
|
+
|
|
38
|
+
export function loadFixture(path: string): Fixture {
|
|
39
|
+
return parseVerifyFixture(JSON.parse(readFileSync(path, 'utf8')));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function runFixture(
|
|
43
|
+
name: string,
|
|
44
|
+
fixture: Fixture,
|
|
45
|
+
rules: VerifyRulesInput,
|
|
46
|
+
options: Omit<VerifyFixtureOptions, 'rules'> = {},
|
|
47
|
+
): Promise<FixtureResult> {
|
|
48
|
+
const result = await verifyFixture(fixture, { rules, ...options });
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
...(fixture.description !== undefined ? { description: fixture.description } : {}),
|
|
52
|
+
ok: result.ok,
|
|
53
|
+
result,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function checkDirectory(
|
|
58
|
+
dir: string,
|
|
59
|
+
rules: VerifyRulesInput,
|
|
60
|
+
options: Omit<VerifyFixtureOptions, 'rules'> = {},
|
|
61
|
+
): Promise<{ results: FixtureResult[]; allOk: boolean }> {
|
|
62
|
+
const files = readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
63
|
+
const results = await Promise.all(
|
|
64
|
+
files.map((f) => runFixture(basename(f, '.json'), loadFixture(join(dir, f)), rules, options)),
|
|
65
|
+
);
|
|
66
|
+
return { results, allOk: results.every((r) => r.ok) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function formatResults(results: FixtureResult[]): string {
|
|
70
|
+
const lines: string[] = [];
|
|
71
|
+
for (const result of results) {
|
|
72
|
+
const marker = result.ok ? '✓' : '✗';
|
|
73
|
+
const desc = result.description ? ` - ${result.description}` : '';
|
|
74
|
+
const serviceSummary = Object.values(result.result.services)
|
|
75
|
+
.filter((service): service is VerifyServiceResult => service !== undefined)
|
|
76
|
+
.map((service) => {
|
|
77
|
+
const failures = failingDivergences(service.divergences).length;
|
|
78
|
+
const info = service.divergences.length - failures;
|
|
79
|
+
return service.ok
|
|
80
|
+
? `${service.service}: ok (${info} informational)`
|
|
81
|
+
: `${service.service}: ${failures} failure(s)`;
|
|
82
|
+
})
|
|
83
|
+
.join(', ');
|
|
84
|
+
lines.push(`${marker} ${result.name}${desc} - ${serviceSummary}`);
|
|
85
|
+
for (const service of Object.values(result.result.services)) {
|
|
86
|
+
if (!service) continue;
|
|
87
|
+
for (const divergence of failingDivergences(service.divergences)) {
|
|
88
|
+
lines.push(` [${service.service}] ${formatDivergence(divergence)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function runVerify(parsed: ParsedArgs, deps: VerifyCliDeps = {}): Promise<number> {
|
|
96
|
+
const cwd = process.cwd();
|
|
97
|
+
const json = parsed.flags.get('json') === true;
|
|
98
|
+
|
|
99
|
+
if (parsed.positional[0] === 'cases') {
|
|
100
|
+
return runVerifyCases(parsed, cwd);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const target = parsed.positional[0];
|
|
104
|
+
const inputPath = resolve(cwd, target ?? SERVE_CAPTURE_PATH);
|
|
105
|
+
if (!existsSync(inputPath)) {
|
|
106
|
+
if (target) {
|
|
107
|
+
process.stderr.write(`pyric verify: no such fixture or directory: ${inputPath}\n`);
|
|
108
|
+
} else {
|
|
109
|
+
process.stderr.write(
|
|
110
|
+
`pyric verify: no captured session at ${SERVE_CAPTURE_PATH}.\n` +
|
|
111
|
+
' Run `pyric dev`, exercise your app, then `pyric verify`, or pass a fixture path.\n',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let loaded: Array<{ name: string; fixture: Fixture }>;
|
|
118
|
+
try {
|
|
119
|
+
loaded = statSync(inputPath).isDirectory()
|
|
120
|
+
? loadFixturesFromDirectory(inputPath)
|
|
121
|
+
: [{ name: basename(inputPath, '.json'), fixture: loadFixture(inputPath) }];
|
|
122
|
+
} catch (e) {
|
|
123
|
+
process.stderr.write(`pyric verify: failed to load fixture: ${messageOf(e)}\n`);
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
if (loaded.length === 0) {
|
|
127
|
+
process.stderr.write(`pyric verify: no .json fixtures in ${inputPath}\n`);
|
|
128
|
+
return 2;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let services: VerifiableService[];
|
|
132
|
+
try {
|
|
133
|
+
services = selectedServices(parsed.flags.get('service'), loaded.map((item) => item.fixture));
|
|
134
|
+
} catch (e) {
|
|
135
|
+
process.stderr.write(`pyric verify: ${messageOf(e)}\n`);
|
|
136
|
+
return 2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let engines: VerifyEngine[];
|
|
140
|
+
try {
|
|
141
|
+
engines = parseEngines(parsed.flags.get('engine'));
|
|
142
|
+
} catch (e) {
|
|
143
|
+
process.stderr.write(`pyric verify: ${messageOf(e)}\n`);
|
|
144
|
+
return 2;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let rulesResolution: { rules: VerifyRulesInput; paths: Partial<Record<VerifiableService, string>> };
|
|
148
|
+
try {
|
|
149
|
+
rulesResolution = await resolveCandidateRules(cwd, services, parsed.flags.get('rules'));
|
|
150
|
+
} catch (e) {
|
|
151
|
+
process.stderr.write(`pyric verify: ${messageOf(e)}\n`);
|
|
152
|
+
return 2;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let rulesTestApi: VerifyFixtureOptions['rulesTestApi'];
|
|
156
|
+
if (engines.includes('rulesTestApi')) {
|
|
157
|
+
try {
|
|
158
|
+
const resolved = await (deps.resolveScope ?? resolveScope)({
|
|
159
|
+
projectId: stringFlag(parsed.flags.get('project')),
|
|
160
|
+
});
|
|
161
|
+
rulesTestApi = { scope: resolved.scope };
|
|
162
|
+
} catch (e) {
|
|
163
|
+
process.stderr.write(`pyric verify: ${messageOf(e)}\n`);
|
|
164
|
+
return 2;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let results: FixtureResult[];
|
|
169
|
+
try {
|
|
170
|
+
results = await Promise.all(
|
|
171
|
+
loaded.map((item) =>
|
|
172
|
+
runFixture(item.name, item.fixture, rulesResolution.rules, {
|
|
173
|
+
services,
|
|
174
|
+
engines,
|
|
175
|
+
...(rulesTestApi ? { rulesTestApi } : {}),
|
|
176
|
+
})),
|
|
177
|
+
);
|
|
178
|
+
} catch (e) {
|
|
179
|
+
const prefix = e instanceof VerifyInputError ? 'invalid input' : 'failed to replay';
|
|
180
|
+
process.stderr.write(`pyric verify: ${prefix}: ${messageOf(e)}\n`);
|
|
181
|
+
return 2;
|
|
182
|
+
}
|
|
183
|
+
const allOk = results.every((result) => result.ok);
|
|
184
|
+
|
|
185
|
+
if (json) {
|
|
186
|
+
process.stdout.write(
|
|
187
|
+
JSON.stringify({
|
|
188
|
+
ok: allOk,
|
|
189
|
+
engines,
|
|
190
|
+
rulesPaths: rulesResolution.paths,
|
|
191
|
+
results: results.map((result) => ({
|
|
192
|
+
name: result.name,
|
|
193
|
+
description: result.description,
|
|
194
|
+
ok: result.ok,
|
|
195
|
+
services: result.result.services,
|
|
196
|
+
})),
|
|
197
|
+
}) + '\n',
|
|
198
|
+
);
|
|
199
|
+
} else {
|
|
200
|
+
process.stdout.write(formatResults(results) + '\n');
|
|
201
|
+
if (!allOk) {
|
|
202
|
+
const failed = results.filter((result) => !result.ok).length;
|
|
203
|
+
const rules = Object.entries(rulesResolution.paths)
|
|
204
|
+
.map(([service, path]) => `${service}=${basename(path)}`)
|
|
205
|
+
.join(', ');
|
|
206
|
+
process.stderr.write(
|
|
207
|
+
`\n✗ ${failed} session(s) regressed under ${rules}.\n`,
|
|
208
|
+
);
|
|
209
|
+
} else {
|
|
210
|
+
process.stdout.write('\n✓ all selected services replay cleanly.\n');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return allOk ? 0 : 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function runVerifyCases(parsed: ParsedArgs, cwd: string): number {
|
|
217
|
+
const target = parsed.positional[1];
|
|
218
|
+
const inputPath = resolve(cwd, target ?? SERVE_CAPTURE_PATH);
|
|
219
|
+
if (!existsSync(inputPath)) {
|
|
220
|
+
process.stderr.write(`pyric verify cases: no such fixture: ${inputPath}\n`);
|
|
221
|
+
return 2;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const services = flagStrings(parsed.flags.get('service')).map(toVerifiableService);
|
|
225
|
+
if (services.some((service) => service !== 'firestore')) {
|
|
226
|
+
process.stderr.write('pyric verify cases: only --service firestore is supported for Rules Test API case derivation.\n');
|
|
227
|
+
return 2;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let fixture: Fixture;
|
|
231
|
+
try {
|
|
232
|
+
fixture = loadFixture(inputPath);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
process.stderr.write(`pyric verify cases: failed to load fixture: ${messageOf(e)}\n`);
|
|
235
|
+
return 2;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const result = deriveRulesTestCases(fixture, {
|
|
239
|
+
service: 'firestore',
|
|
240
|
+
});
|
|
241
|
+
const output = JSON.stringify(result, null, 2) + '\n';
|
|
242
|
+
const out = stringFlag(parsed.flags.get('out'));
|
|
243
|
+
if (out) {
|
|
244
|
+
writeFileSync(resolve(cwd, out), output);
|
|
245
|
+
} else {
|
|
246
|
+
process.stdout.write(output);
|
|
247
|
+
}
|
|
248
|
+
return result.ok ? 0 : 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function loadFixturesFromDirectory(dir: string): Array<{ name: string; fixture: Fixture }> {
|
|
252
|
+
return readdirSync(dir)
|
|
253
|
+
.filter((f) => f.endsWith('.json'))
|
|
254
|
+
.sort()
|
|
255
|
+
.map((f) => ({ name: basename(f, '.json'), fixture: loadFixture(join(dir, f)) }));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function selectedServices(flag: FlagValue | undefined, fixtures: Fixture[]): VerifiableService[] {
|
|
259
|
+
const explicit = flagStrings(flag);
|
|
260
|
+
if (explicit.length > 0) {
|
|
261
|
+
return explicit.map(toVerifiableService);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const union = new Set<VerifiableService>();
|
|
265
|
+
for (const fixture of fixtures) {
|
|
266
|
+
for (const service of fixtureVerifiableServices(fixture)) {
|
|
267
|
+
union.add(service);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (union.size === 0) {
|
|
271
|
+
throw new Error('fixture does not contain firestore or rtdb rules services.');
|
|
272
|
+
}
|
|
273
|
+
return [...union].sort();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function parseEngines(flag: FlagValue | undefined): VerifyEngine[] {
|
|
277
|
+
const raw = flagStrings(flag);
|
|
278
|
+
if (raw.length === 0) return ['sandbox'];
|
|
279
|
+
const out: VerifyEngine[] = [];
|
|
280
|
+
for (const item of raw) {
|
|
281
|
+
if (item === 'both') {
|
|
282
|
+
pushUnique(out, 'sandbox');
|
|
283
|
+
pushUnique(out, 'rulesTestApi');
|
|
284
|
+
} else if (item === 'sandbox') {
|
|
285
|
+
pushUnique(out, 'sandbox');
|
|
286
|
+
} else if (item === 'rules-test-api' || item === 'rulesTestApi') {
|
|
287
|
+
pushUnique(out, 'rulesTestApi');
|
|
288
|
+
} else {
|
|
289
|
+
throw new Error(`unsupported verify engine '${item}'. Supported engines: sandbox, rules-test-api, both.`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function pushUnique<T>(items: T[], item: T): void {
|
|
296
|
+
if (!items.includes(item)) items.push(item);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function resolveCandidateRules(
|
|
300
|
+
cwd: string,
|
|
301
|
+
services: VerifiableService[],
|
|
302
|
+
rulesFlag: FlagValue | undefined,
|
|
303
|
+
): Promise<{ rules: VerifyRulesInput; paths: Partial<Record<VerifiableService, string>> }> {
|
|
304
|
+
const overrides = parseRulesOverrides(rulesFlag);
|
|
305
|
+
const needsDefaults = services.some((service) => !overrides.has(service));
|
|
306
|
+
const firebaseJson = needsDefaults ? await readFirebaseJsonOrNull(cwd) : null;
|
|
307
|
+
|
|
308
|
+
const rules: VerifyRulesInput = {};
|
|
309
|
+
const paths: Partial<Record<VerifiableService, string>> = {};
|
|
310
|
+
for (const service of services) {
|
|
311
|
+
const rel = overrides.get(service) ?? defaultRulesPath(firebaseJson, service);
|
|
312
|
+
if (!rel) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`missing candidate ${service} rules. Pass --rules ${service}=<path> or configure firebase.json.`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const path = resolve(cwd, rel);
|
|
318
|
+
if (!existsSync(path)) {
|
|
319
|
+
throw new Error(`rules file not found for ${service}: ${path}`);
|
|
320
|
+
}
|
|
321
|
+
paths[service] = path;
|
|
322
|
+
if (service === 'firestore') {
|
|
323
|
+
rules.firestore = readFileSync(path, 'utf8');
|
|
324
|
+
} else if (service === 'rtdb') {
|
|
325
|
+
rules.rtdb = parseRtdbRulesFile(path);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return { rules, paths };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function parseRulesOverrides(flag: FlagValue | undefined): Map<VerifiableService, string> {
|
|
332
|
+
const out = new Map<VerifiableService, string>();
|
|
333
|
+
for (const raw of flagStrings(flag)) {
|
|
334
|
+
const eq = raw.indexOf('=');
|
|
335
|
+
if (eq <= 0 || eq === raw.length - 1) {
|
|
336
|
+
throw new Error(`--rules must use service-qualified values like --rules rtdb=database.rules.json.`);
|
|
337
|
+
}
|
|
338
|
+
const service = toVerifiableService(raw.slice(0, eq));
|
|
339
|
+
out.set(service, raw.slice(eq + 1));
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function defaultRulesPath(config: FirebaseJson | null, service: VerifiableService): string | undefined {
|
|
345
|
+
if (service === 'firestore') return config?.firestore?.rules;
|
|
346
|
+
if (service === 'rtdb') return config?.database?.rules;
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function readFirebaseJsonOrNull(cwd: string): Promise<FirebaseJson | null> {
|
|
351
|
+
try {
|
|
352
|
+
return await readFirebaseJson(cwd);
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function parseRtdbRulesFile(path: string): { rules: Record<string, unknown> } {
|
|
359
|
+
let parsed: unknown;
|
|
360
|
+
try {
|
|
361
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
362
|
+
} catch (e) {
|
|
363
|
+
throw new Error(`failed to parse RTDB rules JSON at ${path}: ${messageOf(e)}`);
|
|
364
|
+
}
|
|
365
|
+
return parseRtdbRulesJson(
|
|
366
|
+
parsed,
|
|
367
|
+
() => new Error(`RTDB rules file must contain a top-level "rules" object: ${path}`),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function toVerifiableService(raw: string): VerifiableService {
|
|
372
|
+
if (raw === 'firestore' || raw === 'rtdb') return raw;
|
|
373
|
+
if (raw === 'database') return 'rtdb';
|
|
374
|
+
throw new Error(`unsupported verify service '${raw}'. Supported services: firestore, rtdb.`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function flagStrings(value: FlagValue | undefined): string[] {
|
|
378
|
+
const values = Array.isArray(value) ? value : [value];
|
|
379
|
+
return values.filter((item): item is string => typeof item === 'string' && item.length > 0);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function stringFlag(value: FlagValue | undefined): string | undefined {
|
|
383
|
+
if (Array.isArray(value)) return value.find((item): item is string => typeof item === 'string');
|
|
384
|
+
return typeof value === 'string' ? value : undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function failingDivergences(divergences: VerifyDivergence[]): VerifyDivergence[] {
|
|
388
|
+
return divergences.filter((divergence) => divergence.kind !== 'expected-drift');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function formatDivergence(divergence: VerifyDivergence): string {
|
|
392
|
+
if (divergence.kind === 'now-denied') {
|
|
393
|
+
const method = divergence.method ? `${divergence.method} ` : '';
|
|
394
|
+
return `now-denied: ${method}${divergence.path ?? ''}${divergence.reason ? ` (${divergence.reason})` : ''}`;
|
|
395
|
+
}
|
|
396
|
+
if (divergence.kind === 'now-allowed') {
|
|
397
|
+
const method = divergence.method ? `${divergence.method} ` : '';
|
|
398
|
+
return `now-allowed: ${method}${divergence.path ?? ''}${divergence.reason ? ` (${divergence.reason})` : ''}`;
|
|
399
|
+
}
|
|
400
|
+
if (divergence.kind === 'state-drift') {
|
|
401
|
+
const where = `${divergence.path ?? ''}${divergence.field ? `.${divergence.field}` : ''}`;
|
|
402
|
+
return `state-drift: ${where}: ${JSON.stringify(divergence.before)} -> ${JSON.stringify(divergence.after)}`;
|
|
403
|
+
}
|
|
404
|
+
if (divergence.kind === 'unsupported') {
|
|
405
|
+
return `unsupported: ${divergence.method ?? 'operation'} ${divergence.path ?? ''}: ${divergence.reason}`;
|
|
406
|
+
}
|
|
407
|
+
if (divergence.kind === 'engine-drift') {
|
|
408
|
+
const method = divergence.method ? `${divergence.method} ` : '';
|
|
409
|
+
return `engine-drift: ${method}${divergence.path ?? ''}: sandbox=${divergence.sandbox}, rulesTestApi=${divergence.rulesTestApi}`;
|
|
410
|
+
}
|
|
411
|
+
return `${divergence.drift}: ${divergence.path ?? ''}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function messageOf(value: unknown): string {
|
|
415
|
+
return value instanceof Error ? value.message : String(value);
|
|
416
|
+
}
|