@pyric/cli 0.1.0-alpha.25 → 0.1.0-alpha.27
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/README.md +1 -1
- package/dist/assurance/.generated/conformance-verdicts.d.ts +5 -0
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +5 -0
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/assurance/capabilities.d.ts.map +1 -1
- package/dist/assurance/capabilities.js +24 -5
- package/dist/assurance/capabilities.js.map +1 -1
- package/dist/assurance/runner.d.ts.map +1 -1
- package/dist/assurance/runner.js +37 -50
- package/dist/assurance/runner.js.map +1 -1
- package/dist/assurance/types.d.ts +5 -0
- package/dist/assurance/types.d.ts.map +1 -1
- package/dist/assurance/types.js.map +1 -1
- package/dist/assurance/validation.d.ts +2 -16
- package/dist/assurance/validation.d.ts.map +1 -1
- package/dist/assurance/validation.js +121 -1
- package/dist/assurance/validation.js.map +1 -1
- package/dist/bridge/surface/arguments/assurance.d.ts +25 -0
- package/dist/bridge/surface/arguments/assurance.d.ts.map +1 -1
- package/dist/bridge/surface/arguments/assurance.js +1 -0
- package/dist/bridge/surface/arguments/assurance.js.map +1 -1
- package/dist/bridge/surface/methods/assurance/define.d.ts +2 -2
- package/dist/bridge/surface/methods/assurance/map.d.ts +23 -0
- package/dist/bridge/surface/methods/assurance/map.d.ts.map +1 -1
- package/dist/bridge/surface/methods/assurance/propose.d.ts +7 -0
- package/dist/bridge/surface/methods/assurance/propose.d.ts.map +1 -1
- package/dist/bridge/surface/render/discriminator-schemas.d.ts +2 -2
- package/dist/cli/database-rules.js +1 -1
- package/dist/cli/database-rules.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +13 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pyric-config.d.ts +8 -0
- package/dist/cli/pyric-config.d.ts.map +1 -1
- package/dist/cli/pyric-config.js.map +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +5 -3
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/verify.d.ts +6 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +240 -15
- package/dist/cli/verify.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +1 -1
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
- package/dist/conformance/.generated/conformance-docs.js +1 -1
- package/dist/conformance/.generated/conformance-docs.js.map +1 -1
- package/dist/rtdb/inspect-deployed-rules.d.ts +3 -17
- package/dist/rtdb/inspect-deployed-rules.d.ts.map +1 -1
- package/dist/rtdb/inspect-deployed-rules.js +4 -54
- package/dist/rtdb/inspect-deployed-rules.js.map +1 -1
- package/dist/serve/repro/denials.d.ts.map +1 -1
- package/dist/serve/repro/denials.js +2 -1
- package/dist/serve/repro/denials.js.map +1 -1
- package/dist/serve/site-ui/_astro/{app.BpEphDR7.js → app.CiMHEvEW.js} +15 -15
- package/dist/serve/site-ui/_astro/{app.DFdWhpVY.js → app.DpzsBfos.js} +1 -1
- package/dist/serve/site-ui/_astro/{seed.vvXV1ioI.js → seed.BpXSRzLo.js} +1 -1
- package/dist/serve/site-ui/auth/index.html +1 -1
- package/dist/serve/site-ui/docs/database-compat/index.html +4 -0
- package/dist/serve/site-ui/docs/database-compat.md +4 -0
- package/dist/serve/site-ui/docs/get-started/start-building/index.html +1 -0
- package/dist/serve/site-ui/docs/get-started/start-building.md +2 -0
- package/dist/serve/site-ui/docs/index.json +35 -0
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +23 -4
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api.md +11 -4
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +15 -2
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +11 -10
- package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +372 -201
- package/dist/serve/site-ui/docs/pyric-rules-reference-api.md +148 -9
- package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +56 -1
- package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript.md +33 -0
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite.md +1 -1
- package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +4 -1
- package/dist/serve/site-ui/docs/ship/ship-to-production.md +7 -1
- package/dist/serve/site-ui/examples/chess/index.html +1 -1
- package/dist/serve/site-ui/firestore/index.html +1 -1
- package/dist/serve/site-ui/rtdb/index.html +1 -1
- package/dist/serve/site-ui/settings/index.html +1 -1
- package/dist/serve/site-ui/storage/index.html +1 -1
- package/dist/serve/site-ui/studio/index.html +1 -1
- package/dist/serve/site-ui/traffic/index.html +1 -1
- package/dist/serve/verify-hint.d.ts +2 -0
- package/dist/serve/verify-hint.d.ts.map +1 -0
- package/dist/serve/verify-hint.js +10 -0
- package/dist/serve/verify-hint.js.map +1 -0
- package/dist/serve/vite-generation-rules-watch.d.ts.map +1 -1
- package/dist/serve/vite-generation-rules-watch.js +5 -3
- package/dist/serve/vite-generation-rules-watch.js.map +1 -1
- package/dist/verify/index.d.ts +16 -2
- package/dist/verify/index.d.ts.map +1 -1
- package/dist/verify/index.js +100 -18
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/staging/engine.d.ts +102 -0
- package/dist/verify/staging/engine.d.ts.map +1 -0
- package/dist/verify/staging/engine.js +371 -0
- package/dist/verify/staging/engine.js.map +1 -0
- package/dist/verify/staging/rest.d.ts +68 -0
- package/dist/verify/staging/rest.d.ts.map +1 -0
- package/dist/verify/staging/rest.js +93 -0
- package/dist/verify/staging/rest.js.map +1 -0
- package/dist/verify/staging/target.d.ts +39 -0
- package/dist/verify/staging/target.d.ts.map +1 -0
- package/dist/verify/staging/target.js +117 -0
- package/dist/verify/staging/target.js.map +1 -0
- package/package.json +4 -4
- package/src/assurance/.generated/conformance-verdicts.ts +5 -0
- package/src/assurance/capabilities.ts +22 -4
- package/src/assurance/runner.ts +40 -58
- package/src/assurance/types.ts +3 -0
- package/src/assurance/validation.ts +137 -0
- package/src/bridge/surface/arguments/assurance.ts +1 -0
- package/src/cli/database-rules.ts +1 -1
- package/src/cli/index.ts +13 -2
- package/src/cli/pyric-config.ts +5 -0
- package/src/cli/serve.ts +6 -3
- package/src/cli/verify.ts +244 -18
- package/src/conformance/.generated/can-i-use.ts +1 -1
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/rtdb/inspect-deployed-rules.ts +5 -77
- package/src/serve/repro/denials.ts +2 -1
- package/src/serve/verify-hint.ts +8 -0
- package/src/serve/vite-generation-rules-watch.ts +6 -3
- package/src/verify/index.ts +119 -19
- package/src/verify/staging/engine.ts +455 -0
- package/src/verify/staging/rest.ts +129 -0
- package/src/verify/staging/target.ts +137 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The staging target `pyric verify --engine staging` replays against, from
|
|
3
|
+
* the project's `pyric.json`:
|
|
4
|
+
*
|
|
5
|
+
* { "verify": { "staging": { "project": "my-staging", "rtdb": "https://my-verify.firebaseio.com" } } }
|
|
6
|
+
*
|
|
7
|
+
* `rtdb` is a database URL or an instance name (`my-verify` resolves to
|
|
8
|
+
* `https://my-verify.firebaseio.com`; an instance outside us-central1 needs
|
|
9
|
+
* its full URL). The user brings the instance; pyric provisions nothing.
|
|
10
|
+
*
|
|
11
|
+
* An instance the app's own `firebase.json` deploys rules to, or the app
|
|
12
|
+
* project's default instance, is refused: those hold the app's data.
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
import { resolveDatabaseInstance } from 'pyric/sandbox/internal';
|
|
17
|
+
|
|
18
|
+
import { firebaseToolsConfigstorePath } from '../../cli/firebase-project.js';
|
|
19
|
+
import type { FirebaseJson } from '../../cli/firebase-json.js';
|
|
20
|
+
import { resolveScope, SERVICE_ACCOUNT_BASE64_ENV_KEY, SERVICE_ACCOUNT_FILE_ENV_KEY } from '../../credentials/node/scope.js';
|
|
21
|
+
|
|
22
|
+
export interface StagingTarget {
|
|
23
|
+
projectId: string;
|
|
24
|
+
databaseUrl: string;
|
|
25
|
+
/** The instance name the URL names. */
|
|
26
|
+
instance: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class StagingConfigError extends Error {
|
|
30
|
+
constructor(message: string) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'StagingConfigError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CONFIG_EXAMPLE = '{ "verify": { "staging": { "project": "<staging project id>", "rtdb": "https://<dedicated instance>.firebaseio.com" } } }';
|
|
37
|
+
|
|
38
|
+
/** The staging target in a parsed `pyric.json`, or a config error naming what is missing. */
|
|
39
|
+
export function stagingTargetFrom(config: unknown): StagingTarget {
|
|
40
|
+
const staging = (config as { verify?: { staging?: unknown } } | null)?.verify?.staging;
|
|
41
|
+
if (staging === undefined) {
|
|
42
|
+
throw new StagingConfigError(`no staging target. Add one to pyric.json: ${CONFIG_EXAMPLE}`);
|
|
43
|
+
}
|
|
44
|
+
const { project, rtdb } = (staging ?? {}) as { project?: unknown; rtdb?: unknown };
|
|
45
|
+
if (typeof project !== 'string' || project.trim() === '') {
|
|
46
|
+
throw new StagingConfigError(`pyric.json verify.staging.project must name the staging project: ${CONFIG_EXAMPLE}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof rtdb !== 'string' || rtdb.trim() === '') {
|
|
49
|
+
throw new StagingConfigError(`pyric.json verify.staging.rtdb must name the dedicated instance: ${CONFIG_EXAMPLE}`);
|
|
50
|
+
}
|
|
51
|
+
const trimmed = rtdb.trim();
|
|
52
|
+
const databaseUrl = /^[a-z0-9-]+$/i.test(trimmed) ? `https://${trimmed}.firebaseio.com` : trimmed.replace(/\/+$/, '');
|
|
53
|
+
let instance: string | undefined;
|
|
54
|
+
try {
|
|
55
|
+
instance = resolveDatabaseInstance(databaseUrl)?.name;
|
|
56
|
+
} catch {
|
|
57
|
+
instance = undefined;
|
|
58
|
+
}
|
|
59
|
+
if (instance === undefined) {
|
|
60
|
+
throw new StagingConfigError(`pyric.json verify.staging.rtdb "${rtdb}" is not a Realtime Database URL or instance name.`);
|
|
61
|
+
}
|
|
62
|
+
return { projectId: project.trim(), databaseUrl, instance };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Refuse a staging instance the app uses: one `firebase.json` deploys rules
|
|
67
|
+
* to (by instance or by a `.firebaserc` target), or the default instance of
|
|
68
|
+
* the app's project.
|
|
69
|
+
*/
|
|
70
|
+
export function assertNotAppInstance(
|
|
71
|
+
target: StagingTarget,
|
|
72
|
+
app: { firebaseJson: FirebaseJson | null; appProjectId?: string; targetInstances?: string[] },
|
|
73
|
+
): void {
|
|
74
|
+
const declared = new Set<string>(app.targetInstances ?? []);
|
|
75
|
+
const database = app.firebaseJson?.database as unknown;
|
|
76
|
+
const entries = Array.isArray(database) ? database : database ? [database] : [];
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const instance = (entry as { instance?: unknown }).instance;
|
|
79
|
+
if (typeof instance === 'string') declared.add(instance);
|
|
80
|
+
}
|
|
81
|
+
if (app.appProjectId) declared.add(`${app.appProjectId}-default-rtdb`);
|
|
82
|
+
if (declared.has(target.instance)) {
|
|
83
|
+
throw new StagingConfigError(
|
|
84
|
+
`refusing staging instance ${target.instance}: the app uses it (firebase.json deploys rules to it, or it is the default instance of project ${app.appProjectId}). The staging engine replaces an instance's rules and data, so it needs one dedicated to it.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* An OAuth token for the staging project: a service account or Application
|
|
91
|
+
* Default Credentials (`resolveScope`), else the Firebase CLI's login
|
|
92
|
+
* (`firebase login`) while its access token is fresh.
|
|
93
|
+
*/
|
|
94
|
+
export async function stagingToken(
|
|
95
|
+
projectId: string,
|
|
96
|
+
deps: { resolve?: typeof resolveScope; env?: NodeJS.ProcessEnv; now?: () => number } = {},
|
|
97
|
+
): Promise<{ token: () => Promise<string>; source: string }> {
|
|
98
|
+
const env = deps.env ?? process.env;
|
|
99
|
+
try {
|
|
100
|
+
const { scope, source } = await (deps.resolve ?? resolveScope)({ projectId, ...(deps.env ? { env: deps.env } : {}) });
|
|
101
|
+
return { token: () => scope.resolveToken(), source };
|
|
102
|
+
} catch (resolveError) {
|
|
103
|
+
// A configured credential that fails is an error to fix, not a reason to
|
|
104
|
+
// run as the developer's own CLI login instead.
|
|
105
|
+
if (configuredCredential(env)) throw resolveError;
|
|
106
|
+
const cli = firebaseCliToken(env, (deps.now ?? Date.now)());
|
|
107
|
+
if (cli !== undefined) return { token: async () => cli, source: 'firebase login' };
|
|
108
|
+
throw new StagingConfigError(
|
|
109
|
+
`no credentials for staging project ${projectId}. ${resolveError instanceof Error ? resolveError.message : String(resolveError)} ` +
|
|
110
|
+
'Or sign in with `firebase login`: its token is used while it has at least 15 minutes left (run any firebase command to refresh it).',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Whether the environment names a service account (`resolveScope`'s first two sources). */
|
|
116
|
+
function configuredCredential(env: NodeJS.ProcessEnv): boolean {
|
|
117
|
+
return [SERVICE_ACCOUNT_BASE64_ENV_KEY, SERVICE_ACCOUNT_FILE_ENV_KEY].some((key) => (env[key] ?? '').trim() !== '');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** How long a CLI token must stay valid for a run to start with it; it cannot be refreshed mid-run. */
|
|
121
|
+
export const CLI_TOKEN_MARGIN_MS = 15 * 60_000;
|
|
122
|
+
|
|
123
|
+
/** The Firebase CLI's access token, when its configstore holds one with a known expiry at least the margin away. */
|
|
124
|
+
function firebaseCliToken(env: NodeJS.ProcessEnv, now: number): string | undefined {
|
|
125
|
+
try {
|
|
126
|
+
const store = JSON.parse(readFileSync(firebaseToolsConfigstorePath(env), 'utf8')) as {
|
|
127
|
+
tokens?: { access_token?: unknown; expires_at?: unknown };
|
|
128
|
+
};
|
|
129
|
+
const token = store.tokens?.access_token;
|
|
130
|
+
const expiresAt = store.tokens?.expires_at;
|
|
131
|
+
if (typeof token !== 'string' || token === '') return undefined;
|
|
132
|
+
if (typeof expiresAt !== 'number' || expiresAt - now < CLI_TOKEN_MARGIN_MS) return undefined;
|
|
133
|
+
return token;
|
|
134
|
+
} catch {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|