primitive-admin 1.0.49 → 1.0.50
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 +102 -2
- package/assets/skill/skills/primitive-platform/SKILL.md +85 -30
- package/dist/bin/primitive.d.ts +2 -0
- package/dist/bin/primitive.js +66 -1
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.d.ts +2 -0
- package/dist/src/commands/analytics.d.ts +2 -0
- package/dist/src/commands/apps.d.ts +2 -0
- package/dist/src/commands/apps.js +20 -0
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/blob-buckets.d.ts +2 -0
- package/dist/src/commands/catalog.d.ts +2 -0
- package/dist/src/commands/collection-type-configs.d.ts +2 -0
- package/dist/src/commands/collections.d.ts +2 -0
- package/dist/src/commands/comparisons.d.ts +2 -0
- package/dist/src/commands/cron-triggers.d.ts +2 -0
- package/dist/src/commands/cron-triggers.js +8 -15
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-types.d.ts +2 -0
- package/dist/src/commands/databases.d.ts +2 -0
- package/dist/src/commands/databases.js +31 -0
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +2 -0
- package/dist/src/commands/email-templates.d.ts +2 -0
- package/dist/src/commands/env.d.ts +12 -0
- package/dist/src/commands/group-type-configs.d.ts +2 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/guides.d.ts +84 -0
- package/dist/src/commands/guides.js +201 -24
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +17 -0
- package/dist/src/commands/init.js +63 -25
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.d.ts +2 -0
- package/dist/src/commands/integrations.js +22 -5
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.d.ts +2 -0
- package/dist/src/commands/prompts.d.ts +2 -0
- package/dist/src/commands/rule-sets.d.ts +2 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/skill.d.ts +2 -0
- package/dist/src/commands/sync.d.ts +113 -0
- package/dist/src/commands/sync.js +366 -12
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.d.ts +2 -0
- package/dist/src/commands/tokens.js +104 -1
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.d.ts +2 -0
- package/dist/src/commands/waitlist.d.ts +2 -0
- package/dist/src/commands/waitlist.js +1 -1
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +2 -0
- package/dist/src/commands/workflows.d.ts +49 -0
- package/dist/src/commands/workflows.js +74 -21
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +1244 -0
- package/dist/src/lib/api-client.js +30 -0
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/auth-flow.d.ts +8 -0
- package/dist/src/lib/cli-manifest.d.ts +60 -0
- package/dist/src/lib/cli-manifest.js +70 -0
- package/dist/src/lib/cli-manifest.js.map +1 -0
- package/dist/src/lib/config.d.ts +37 -0
- package/dist/src/lib/confirm-prompt.d.ts +66 -0
- package/dist/src/lib/confirm-prompt.js +85 -0
- package/dist/src/lib/confirm-prompt.js.map +1 -0
- package/dist/src/lib/constants.d.ts +2 -0
- package/dist/src/lib/crash-handlers.d.ts +20 -0
- package/dist/src/lib/crash-handlers.js +49 -0
- package/dist/src/lib/crash-handlers.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +79 -0
- package/dist/src/lib/csv.d.ts +48 -0
- package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
- package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +31 -10
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
- package/dist/src/lib/env-resolver.d.ts +62 -0
- package/dist/src/lib/fetch.d.ts +5 -0
- package/dist/src/lib/init-config.d.ts +46 -0
- package/dist/src/lib/init-config.js +7 -0
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/migration-nag.d.ts +49 -0
- package/dist/src/lib/output.d.ts +49 -0
- package/dist/src/lib/output.js +25 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +33 -0
- package/dist/src/lib/project-config.d.ts +97 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
- package/dist/src/lib/resolve-platform.d.ts +45 -0
- package/dist/src/lib/resolve-platform.js +43 -0
- package/dist/src/lib/resolve-platform.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +23 -0
- package/dist/src/lib/snapshots.d.ts +99 -0
- package/dist/src/lib/snapshots.js +357 -0
- package/dist/src/lib/snapshots.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +72 -0
- package/dist/src/lib/sync-paths.js +29 -1
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/template.d.ts +93 -0
- package/dist/src/lib/token-inject.d.ts +56 -0
- package/dist/src/lib/token-inject.js +204 -0
- package/dist/src/lib/token-inject.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +132 -0
- package/dist/src/lib/toml-params-validator.d.ts +95 -0
- package/dist/src/lib/version-check.d.ts +10 -0
- package/dist/src/lib/workflow-fragments.d.ts +41 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +86 -0
- package/dist/src/lib/workflow-toml-validator.js +31 -1
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +513 -0
- package/dist/src/validators.d.ts +64 -0
- package/dist/src/validators.js +63 -0
- package/dist/src/validators.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the `primitive tokens inject` command (issue #420).
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from the commander wrapper in `commands/tokens.ts` so the
|
|
5
|
+
* snippet building, shared-secret generation, and one-shot localhost server
|
|
6
|
+
* can be unit-tested without a live backend or a spawned CLI process.
|
|
7
|
+
*/
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
/** Default TTL for injected tokens — short-lived per the #420 design (30 minutes). */
|
|
12
|
+
export const DEFAULT_INJECT_TTL = "30m";
|
|
13
|
+
/** Lowest port the `--serve` one-shot server is allowed to bind. */
|
|
14
|
+
const MIN_SERVE_PORT = 30001;
|
|
15
|
+
/** How many random ports to try before giving up on collisions. */
|
|
16
|
+
const SERVE_PORT_ATTEMPTS = 5;
|
|
17
|
+
/**
|
|
18
|
+
* Escape a token for safe embedding inside a single-quoted JS string literal.
|
|
19
|
+
* Tokens are `prim_<base64url>` so they normally need no escaping, but we are
|
|
20
|
+
* defensive in case the source ever changes.
|
|
21
|
+
*/
|
|
22
|
+
function escapeForJsSingleQuote(value) {
|
|
23
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the browser console one-liner that applies a token via the
|
|
27
|
+
* `window.__PRIMITIVE_SET_TOKEN__` hook installed by `<DevTools>`.
|
|
28
|
+
*/
|
|
29
|
+
export function buildSnippet(token) {
|
|
30
|
+
return `window.__PRIMITIVE_SET_TOKEN__('${escapeForJsSingleQuote(token)}')`;
|
|
31
|
+
}
|
|
32
|
+
/** Render the output for a given `--print` mode. */
|
|
33
|
+
export function buildPrintOutput(mode, token) {
|
|
34
|
+
switch (mode) {
|
|
35
|
+
case "snippet":
|
|
36
|
+
return buildSnippet(token);
|
|
37
|
+
case "token":
|
|
38
|
+
return token;
|
|
39
|
+
case "env":
|
|
40
|
+
return `VITE_PRIMITIVE_DEV_TOKEN=${token}`;
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Invalid --print mode: ${mode}. Expected one of: snippet, env, token.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Generate a random, URL-safe shared secret for the `--serve` endpoint. */
|
|
46
|
+
export function generateSharedSecret() {
|
|
47
|
+
return randomBytes(16).toString("hex");
|
|
48
|
+
}
|
|
49
|
+
/** A random integer in [min, max]. */
|
|
50
|
+
function randomPort() {
|
|
51
|
+
// 30001..65535
|
|
52
|
+
const span = 65535 - MIN_SERVE_PORT;
|
|
53
|
+
return MIN_SERVE_PORT + Math.floor(Math.random() * span);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Copy text to the system clipboard using whatever native tool is available.
|
|
57
|
+
* No new npm dependency — shells out to `pbcopy` (macOS), `xclip`/`xsel`
|
|
58
|
+
* (Linux), or `clip` (Windows). Returns `true` on success, `false` if no
|
|
59
|
+
* clipboard tool is available or the copy failed (caller degrades gracefully).
|
|
60
|
+
*/
|
|
61
|
+
export function copyToClipboard(text) {
|
|
62
|
+
const candidates = process.platform === "darwin"
|
|
63
|
+
? [{ cmd: "pbcopy", args: [] }]
|
|
64
|
+
: process.platform === "win32"
|
|
65
|
+
? [{ cmd: "clip", args: [] }]
|
|
66
|
+
: [
|
|
67
|
+
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
68
|
+
{ cmd: "xsel", args: ["--clipboard", "--input"] },
|
|
69
|
+
{ cmd: "wl-copy", args: [] },
|
|
70
|
+
];
|
|
71
|
+
for (const { cmd, args } of candidates) {
|
|
72
|
+
try {
|
|
73
|
+
const res = spawnSync(cmd, args, { input: text });
|
|
74
|
+
if (res.error)
|
|
75
|
+
continue; // command not found — try next
|
|
76
|
+
if (res.status === 0)
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// try next candidate
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* CORS headers for the one-shot token response. `--serve` is documented to be
|
|
87
|
+
* read via `fetch('http://127.0.0.1:PORT/...')` FROM the app's browser console,
|
|
88
|
+
* which is a cross-origin request. Without `Access-Control-Allow-Origin` the
|
|
89
|
+
* browser blocks `r.text()`, so the advertised browser-injection flow fails.
|
|
90
|
+
* We echo back the request `Origin` (falling back to `*`) and allow `GET`.
|
|
91
|
+
*
|
|
92
|
+
* Note: these headers do NOT weaken the loopback-bind + shared-secret model.
|
|
93
|
+
* CORS only governs whether the browser exposes the RESPONSE to page JS; the
|
|
94
|
+
* 127.0.0.1 bind and the unguessable per-run secret are what gate access.
|
|
95
|
+
*/
|
|
96
|
+
function corsHeaders(reqOrigin) {
|
|
97
|
+
return {
|
|
98
|
+
"Access-Control-Allow-Origin": reqOrigin ?? "*",
|
|
99
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
100
|
+
"Access-Control-Allow-Headers": "*",
|
|
101
|
+
Vary: "Origin",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Boot a one-shot HTTP server bound to `127.0.0.1` that serves `token` exactly
|
|
106
|
+
* once, then shuts down. Security model:
|
|
107
|
+
* - binds loopback only (never 0.0.0.0)
|
|
108
|
+
* - requires a random shared-secret `?key=` query param; requests without the
|
|
109
|
+
* correct secret get 403 and do NOT consume the one-shot read
|
|
110
|
+
* - returns CORS headers so the documented cross-origin browser `fetch` can
|
|
111
|
+
* actually read the response; a CORS preflight (`OPTIONS`) is answered 204
|
|
112
|
+
* WITHOUT consuming the one-shot read or shutting the server down
|
|
113
|
+
* - the one-shot consumption + shutdown happen ONLY on the authorized `GET`
|
|
114
|
+
* that actually delivers the token (correct secret) — never on a preflight,
|
|
115
|
+
* a secret-rejected request, or a wrong path
|
|
116
|
+
* - retries random ports >30000 on collision (>= 5 attempts)
|
|
117
|
+
*/
|
|
118
|
+
export async function serveTokenOnce(token, opts = {}) {
|
|
119
|
+
const secret = generateSharedSecret();
|
|
120
|
+
const path = opts.path ?? "/token";
|
|
121
|
+
let resolveDone;
|
|
122
|
+
const done = new Promise((resolve) => {
|
|
123
|
+
resolveDone = resolve;
|
|
124
|
+
});
|
|
125
|
+
let served = false;
|
|
126
|
+
const server = createServer((req, res) => {
|
|
127
|
+
const reqUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
128
|
+
const cors = corsHeaders(req.headers.origin);
|
|
129
|
+
// CORS preflight: the browser sends an OPTIONS before the cross-origin GET.
|
|
130
|
+
// Answer it with the CORS headers and 204, but do NOT consume the one-shot
|
|
131
|
+
// read or shut the server down — the real GET still needs to deliver the
|
|
132
|
+
// token afterward.
|
|
133
|
+
if (req.method === "OPTIONS") {
|
|
134
|
+
res.writeHead(204, cors);
|
|
135
|
+
res.end();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (reqUrl.pathname !== path) {
|
|
139
|
+
res.writeHead(404, { "Content-Type": "text/plain", ...cors });
|
|
140
|
+
res.end("Not found");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Reject any request missing the correct shared secret. Does not consume
|
|
144
|
+
// the one-shot read, so a racing process cannot lock the dev out.
|
|
145
|
+
if (reqUrl.searchParams.get("key") !== secret) {
|
|
146
|
+
res.writeHead(403, { "Content-Type": "text/plain", ...cors });
|
|
147
|
+
res.end("Forbidden");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (served) {
|
|
151
|
+
res.writeHead(410, { "Content-Type": "text/plain", ...cors });
|
|
152
|
+
res.end("Token already served");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Authorized GET with the correct secret: this is the only path that
|
|
156
|
+
// consumes the one-shot read and shuts the server down.
|
|
157
|
+
served = true;
|
|
158
|
+
res.writeHead(200, { "Content-Type": "text/plain", ...cors });
|
|
159
|
+
res.end(token);
|
|
160
|
+
// Close after the response flushes so the single read completes cleanly.
|
|
161
|
+
res.on("finish", () => {
|
|
162
|
+
server.close(() => resolveDone());
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
const port = await listenWithRetry(server);
|
|
166
|
+
const url = `http://127.0.0.1:${port}${path}?key=${secret}`;
|
|
167
|
+
const stop = () => {
|
|
168
|
+
try {
|
|
169
|
+
server.close();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// already closed
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
return { url, secret, port, done, stop };
|
|
176
|
+
}
|
|
177
|
+
/** Try random ports > 30000 until one binds, up to SERVE_PORT_ATTEMPTS times. */
|
|
178
|
+
function listenWithRetry(server) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
let attempts = 0;
|
|
181
|
+
const tryListen = () => {
|
|
182
|
+
attempts += 1;
|
|
183
|
+
const port = randomPort();
|
|
184
|
+
const onError = (err) => {
|
|
185
|
+
server.removeListener("listening", onListening);
|
|
186
|
+
if (err.code === "EADDRINUSE" && attempts < SERVE_PORT_ATTEMPTS) {
|
|
187
|
+
tryListen();
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
reject(new Error(`Failed to bind a local port for --serve after ${attempts} attempt(s): ${err.message}`));
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const onListening = () => {
|
|
194
|
+
server.removeListener("error", onError);
|
|
195
|
+
resolve(port);
|
|
196
|
+
};
|
|
197
|
+
server.once("error", onError);
|
|
198
|
+
server.once("listening", onListening);
|
|
199
|
+
server.listen(port, "127.0.0.1");
|
|
200
|
+
};
|
|
201
|
+
tryListen();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=token-inject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-inject.js","sourceRoot":"","sources":["../../../src/lib/token-inject.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for emitting `DatabaseTypeOperation` configs as native TOML.
|
|
3
|
+
*
|
|
4
|
+
* Background (issue #752): historically `sync pull` serialized
|
|
5
|
+
* `definition` and `params` as JSON strings stuffed into a single TOML
|
|
6
|
+
* field, which produced unreviewable 800-char one-liners. This module
|
|
7
|
+
* lets us emit them as nested TOML tables when they're representable,
|
|
8
|
+
* while preserving the existing file's shape so we don't generate
|
|
9
|
+
* surprise diffs.
|
|
10
|
+
*
|
|
11
|
+
* Three responsibilities:
|
|
12
|
+
* 1. `detectExistingOperationForms()` — read the raw TOML source and
|
|
13
|
+
* decide, per operation name, whether the file uses JSON-string
|
|
14
|
+
* form ("legacy") or nested-table form ("native") for each of
|
|
15
|
+
* `definition` and `params`. This drives form preservation on
|
|
16
|
+
* `sync pull`.
|
|
17
|
+
* 2. `canEmitNative()` — recursively check whether a JS value is
|
|
18
|
+
* representable as a TOML value. Returns a reason string if not.
|
|
19
|
+
* Mainly catches `null` and mixed-type arrays, which `@iarna/toml`
|
|
20
|
+
* either rejects or silently mangles.
|
|
21
|
+
* 3. `buildDatabaseTypeTomlData()` — builds the JS object that we
|
|
22
|
+
* hand to `TOML.stringify`. Per operation, decides JSON-string vs
|
|
23
|
+
* native based on (existing form, can-emit, override mode).
|
|
24
|
+
*/
|
|
25
|
+
export type FieldForm = "native" | "legacy";
|
|
26
|
+
export interface OperationFormHints {
|
|
27
|
+
/** Map of op name → existing form for `definition`. Missing op = "default". */
|
|
28
|
+
definition: Map<string, FieldForm>;
|
|
29
|
+
/** Map of op name → existing form for `params`. Missing op = "default". */
|
|
30
|
+
params: Map<string, FieldForm>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Inspect the raw TOML source for a database-type file and decide which
|
|
34
|
+
* form each operation's `definition`/`params` currently use.
|
|
35
|
+
*
|
|
36
|
+
* Detection is intentionally simple: we walk `[[operations]]` blocks in
|
|
37
|
+
* source order and, for each one, look for either a `definition = '...'`
|
|
38
|
+
* line (legacy) or an `[operations.definition]` / `[operations.definition.X]`
|
|
39
|
+
* sub-header (native).
|
|
40
|
+
*/
|
|
41
|
+
export declare function detectExistingOperationForms(rawToml: string): OperationFormHints;
|
|
42
|
+
/**
|
|
43
|
+
* Recursively check whether a JS value can be safely serialized to TOML
|
|
44
|
+
* using `@iarna/toml`. Returns null if OK, a reason string otherwise.
|
|
45
|
+
*
|
|
46
|
+
* Things TOML can't represent (in `@iarna/toml`):
|
|
47
|
+
* - `null` (TOML has no null).
|
|
48
|
+
* - Arrays containing values of different "TOML types" (e.g. mixed
|
|
49
|
+
* string and number). Spec-wise TOML 1.0 actually allows this, but
|
|
50
|
+
* `@iarna/toml` is strict and throws.
|
|
51
|
+
* - `undefined` (different from null but equally not representable).
|
|
52
|
+
*/
|
|
53
|
+
export declare function canEmitNative(value: any): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Normalize a `params` value into a sorted array of
|
|
56
|
+
* `{ name, type, required, ... }` rows, suitable for emitting as
|
|
57
|
+
* `[[operations.params]]`. Accepts both shapes the wire/parser supports:
|
|
58
|
+
* - object form: `{ projectId: { type: "string", required: true } }`
|
|
59
|
+
* - array form: `[{ name: "projectId", type: "string", required: true }]`
|
|
60
|
+
*/
|
|
61
|
+
export declare function normalizeParamsToArray(params: any): any[] | null;
|
|
62
|
+
export interface BuildOptions {
|
|
63
|
+
/** Per-op form hints derived from `detectExistingOperationForms`. */
|
|
64
|
+
hints?: OperationFormHints;
|
|
65
|
+
/**
|
|
66
|
+
* Override the default form for ops with no hint. Used by `sync pull`
|
|
67
|
+
* to keep behavior backwards-compatible if we ever flip the default,
|
|
68
|
+
* and by `sync migrate-toml` to force native everywhere.
|
|
69
|
+
*/
|
|
70
|
+
defaultForm?: FieldForm;
|
|
71
|
+
/** Sink for human-readable fallback messages. */
|
|
72
|
+
logger?: (message: string) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Server `DatabaseTypeSubscription` rows to emit as `[[subscriptions]]`
|
|
75
|
+
* blocks (issue #803). Already sanitized by the controller's
|
|
76
|
+
* `sanitizeSubscription` (so `select`/`emit` are arrays and `params` is an
|
|
77
|
+
* object). Omitted on the `migrate-toml` path (no subscriptions to merge).
|
|
78
|
+
*/
|
|
79
|
+
subscriptions?: any[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Build the JS object to pass to `TOML.stringify` for a database-type
|
|
83
|
+
* config file. Per operation:
|
|
84
|
+
* - If the chosen form is "native" AND `definition` is TOML-emittable,
|
|
85
|
+
* emit `[operations.definition]` (nested table).
|
|
86
|
+
* - Else emit `definition = '<json string>'` and log a message.
|
|
87
|
+
* - Same logic independently for `params`, but the native form for
|
|
88
|
+
* params is `[[operations.params]]` (array-of-tables).
|
|
89
|
+
*/
|
|
90
|
+
export declare function buildDatabaseTypeTomlData(typeConfig: any, operations: any[], ruleSetIdToName: Map<string, string>, options?: BuildOptions): any;
|
|
91
|
+
/**
|
|
92
|
+
* Thrown when a `[[subscriptions]]` block declares both `accessRule` and
|
|
93
|
+
* `access` with different values. We never silently drop the field
|
|
94
|
+
* (issue #803 maintainer decision): prefer `access` when they agree, error
|
|
95
|
+
* when they conflict so the operator fixes the source of truth.
|
|
96
|
+
*/
|
|
97
|
+
export declare class SubscriptionAccessKeyConflictError extends Error {
|
|
98
|
+
subscriptionKey: string | undefined;
|
|
99
|
+
constructor(subscriptionKey: string | undefined);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the subscription `access` CEL expression from a TOML row.
|
|
103
|
+
*
|
|
104
|
+
* The server's wire key is `access`; the issue author (and the guides) used
|
|
105
|
+
* `accessRule`. We accept both: prefer `access` when present, fall back to
|
|
106
|
+
* `accessRule`, and never silently drop the field. If both are present with
|
|
107
|
+
* different values, throw so the conflict is surfaced rather than guessed.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveSubscriptionAccess(sub: any): string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Normalize a `[[subscriptions]]` TOML row into the server's wire shape.
|
|
112
|
+
*
|
|
113
|
+
* Mirrors `normalizeOperationFromToml`. Maps the `accessRule`→`access`
|
|
114
|
+
* alias (issue #803), and passes `select` / `emit` / `params` through in
|
|
115
|
+
* native form — the server stores them as JSON strings and re-parses on
|
|
116
|
+
* read (`sanitizeSubscription`), so the parse → push → pull round-trip is
|
|
117
|
+
* symmetric with operations' `definition` / `params`.
|
|
118
|
+
*
|
|
119
|
+
* `filter` stays required server-side; we forward whatever is present (or
|
|
120
|
+
* undefined) and let the controller's clean named 400 surface.
|
|
121
|
+
*/
|
|
122
|
+
export declare function normalizeSubscriptionFromToml(sub: any): any;
|
|
123
|
+
/**
|
|
124
|
+
* After parsing TOML, normalize the `definition` and `params` of each
|
|
125
|
+
* operation row into the JS objects the server expects. Handles both
|
|
126
|
+
* native and legacy forms transparently. The server payload is the
|
|
127
|
+
* source of truth; native TOML is purely a source-control affordance.
|
|
128
|
+
*
|
|
129
|
+
* Mirrors `parseDatabaseTypeToml` in `sync.ts`, but is broken out here
|
|
130
|
+
* so the migrate-toml command can call it without re-implementing.
|
|
131
|
+
*/
|
|
132
|
+
export declare function normalizeOperationFromToml(op: any): any;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validator: every `$params.X` reference inside an operation's
|
|
3
|
+
* `definition` must correspond to a declared param in
|
|
4
|
+
* `[[operations.params]]` (or the legacy JSON-string params object).
|
|
5
|
+
*
|
|
6
|
+
* Background (issue #752): a typo like `$params.proectId` used to
|
|
7
|
+
* silently no-op at runtime. With native TOML, we can catch these at
|
|
8
|
+
* `sync push` time with the file path and line number of the
|
|
9
|
+
* operation block where the bad reference appears.
|
|
10
|
+
*
|
|
11
|
+
* Design notes:
|
|
12
|
+
* - Line attribution is per-operation, not per-reference. We don't
|
|
13
|
+
* parse `definition` line-by-line; we just locate the
|
|
14
|
+
* `[[operations]]` header for the named op in the raw source and
|
|
15
|
+
* report that line. This is the load-bearing UX (jump to the
|
|
16
|
+
* offending op block); pinpointing the exact `$params.X` reference
|
|
17
|
+
* inside a sub-table would require swapping the TOML parser, which
|
|
18
|
+
* the issue explicitly leaves to implementer judgement.
|
|
19
|
+
* - We intentionally diverge from the server-side `collectParamRefs`
|
|
20
|
+
* in `database-type-operations-controller.ts:1163`. The server pushes
|
|
21
|
+
* the full path (e.g. `"X.Y"` for `$params.X.Y`); the CLI extracts
|
|
22
|
+
* only the first segment (`"X"`). Net effect: the CLI validator is
|
|
23
|
+
* more lenient than the server — `$params.X.Y` passes the CLI check
|
|
24
|
+
* when `X` alone is declared in `[[operations.params]]`, even though
|
|
25
|
+
* the server treats the full path as the lookup key. The server
|
|
26
|
+
* remains authoritative; the CLI's first-segment extraction is a
|
|
27
|
+
* pragmatic choice so authors can declare structured params (e.g.
|
|
28
|
+
* `config: { type: "object" }`) and reference sub-fields like
|
|
29
|
+
* `$params.config.subKey` without listing every sub-field
|
|
30
|
+
* individually (review feedback r3246635661).
|
|
31
|
+
*/
|
|
32
|
+
export interface ValidationIssue {
|
|
33
|
+
file: string;
|
|
34
|
+
line: number;
|
|
35
|
+
op: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
export interface ValidationResult {
|
|
39
|
+
errors: ValidationIssue[];
|
|
40
|
+
warnings: ValidationIssue[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Walk an arbitrary JSON-ish value and collect `$params.X` references
|
|
44
|
+
* found in string-valued leaves, returning only the first dotted segment
|
|
45
|
+
* (`"X"` for `$params.X.Y`). Intentionally more lenient than the
|
|
46
|
+
* server-side helper, which pushes the full path — see the module
|
|
47
|
+
* doc-comment for the rationale.
|
|
48
|
+
*/
|
|
49
|
+
export declare function collectParamRefs(value: any): string[];
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a `params` value (object, array, or JSON string) into the
|
|
52
|
+
* set of declared param names.
|
|
53
|
+
*/
|
|
54
|
+
export declare function declaredParamNames(params: any): Set<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Find the 1-indexed line number in `rawToml` where the `[[operations]]`
|
|
57
|
+
* block declaring `opName` starts. Returns 0 if not found.
|
|
58
|
+
*
|
|
59
|
+
* Implementation: walk in source order, track the most recent
|
|
60
|
+
* `[[operations]]` header, and when we see a `name = "<opName>"` line
|
|
61
|
+
* before the next `[[operations]]` or top-level header, return that
|
|
62
|
+
* header's line.
|
|
63
|
+
*/
|
|
64
|
+
export declare function locateOperationLine(rawToml: string, opName: string): number;
|
|
65
|
+
export interface ValidateOptions {
|
|
66
|
+
filePath: string;
|
|
67
|
+
rawToml: string;
|
|
68
|
+
/**
|
|
69
|
+
* Operations as parsed for the wire (definition/params already in JS
|
|
70
|
+
* object form). The validator works against the parsed shape so it's
|
|
71
|
+
* indifferent to native vs JSON-string source.
|
|
72
|
+
*/
|
|
73
|
+
operations: Array<{
|
|
74
|
+
name: string;
|
|
75
|
+
definition: any;
|
|
76
|
+
params?: any;
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Validate `$params` references across every operation in a parsed
|
|
81
|
+
* database-type config.
|
|
82
|
+
*
|
|
83
|
+
* Errors (blocking):
|
|
84
|
+
* - A `$params.X` reference inside `definition` where `X` is not
|
|
85
|
+
* declared in `[[operations.params]]`.
|
|
86
|
+
*
|
|
87
|
+
* Warnings (soft, not blocking):
|
|
88
|
+
* - A param declared in `[[operations.params]]` that is not referenced
|
|
89
|
+
* anywhere in `definition`. Authors may be staging a deprecation.
|
|
90
|
+
*/
|
|
91
|
+
export declare function validateOperations(opts: ValidateOptions): ValidationResult;
|
|
92
|
+
/**
|
|
93
|
+
* Format a validation issue as `<file>:<line>: <message>`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function formatIssue(issue: ValidationIssue): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a newer version of the CLI is available on npm.
|
|
3
|
+
*
|
|
4
|
+
* - Stable versions (e.g. "1.0.22") check the "latest" dist-tag
|
|
5
|
+
* - Prerelease versions (e.g. "1.0.23-alpha.3") check their channel's
|
|
6
|
+
* dist-tag (e.g. "alpha"), falling back to "latest" if that tag doesn't exist
|
|
7
|
+
*
|
|
8
|
+
* Caches results per dist-tag for 4 hours. Fails silently on any error.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkForUpdate(currentVersion: string): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-only workflow fragment expansion.
|
|
3
|
+
*
|
|
4
|
+
* Workflows may declare `include = ["fragment-name", ...]` at the top of
|
|
5
|
+
* their TOML. The CLI expands those references into a fully-flattened
|
|
6
|
+
* `[[steps]]` list before push — the server never sees fragments and
|
|
7
|
+
* stores only the canonical, expanded JSON.
|
|
8
|
+
*
|
|
9
|
+
* Pinned decisions (#744):
|
|
10
|
+
* - CLI-only. No server-side WorkflowFragment model.
|
|
11
|
+
* - Fragments live at <workflowDir>/../workflow-fragments/<name>.toml.
|
|
12
|
+
* - Fragment files are `[[steps]]` lists only — no `[workflow]` block.
|
|
13
|
+
* - No recursive includes in v1 (fragments cannot themselves `include`).
|
|
14
|
+
* - Unique step ids validated post-expansion; collisions name both
|
|
15
|
+
* locations in the error message.
|
|
16
|
+
*
|
|
17
|
+
* Wiring: `parseTomlFile()` in `cli/src/commands/sync.ts` calls
|
|
18
|
+
* `expandWorkflowTomlData()` after parsing. All 24 push parse sites in
|
|
19
|
+
* sync.ts route through that single seam, so the expansion is uniform.
|
|
20
|
+
* The standalone `expandWorkflow(filePath)` helper backs the
|
|
21
|
+
* `primitive workflows expand` subcommand for debugging.
|
|
22
|
+
*/
|
|
23
|
+
export interface ExpandedWorkflow {
|
|
24
|
+
workflow?: Record<string, any>;
|
|
25
|
+
steps: Array<Record<string, any>>;
|
|
26
|
+
include?: undefined;
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read and expand a workflow TOML file from disk. Returns the parsed
|
|
31
|
+
* TOML with all `include` fragments spliced in. If the file has no
|
|
32
|
+
* `include` key, the parsed TOML is returned unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function expandWorkflow(workflowPath: string): ExpandedWorkflow;
|
|
35
|
+
/**
|
|
36
|
+
* Expand the `include` key of an already-parsed TOML object. Resolves
|
|
37
|
+
* fragment files relative to `<workflowPath>/../../workflow-fragments/`.
|
|
38
|
+
* Use this when the TOML has already been parsed elsewhere (e.g. inside
|
|
39
|
+
* `parseTomlFile()`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function expandWorkflowTomlData(parsed: Record<string, any>, workflowPath: string): ExpandedWorkflow;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push-time validator for workflow TOML files (issue #685).
|
|
3
|
+
*
|
|
4
|
+
* Detects the common footgun where a user writes `[steps.<id>.<field>]` under
|
|
5
|
+
* an open `[[steps]]` array. TOML parses that as a sub-table on the
|
|
6
|
+
* most-recent step keyed by the step's id (e.g. `steps[0]["refresh-each"]`),
|
|
7
|
+
* not as the intended `steps[0].request`. The result is a step with an
|
|
8
|
+
* unrecognized top-level field — the runtime silently ignores it, and the
|
|
9
|
+
* step then runs with an empty `request` block.
|
|
10
|
+
*
|
|
11
|
+
* The validator walks every step in `tomlData.steps[]` and reports any
|
|
12
|
+
* top-level field not in the universal-and-consumed allowlist. The allowlist
|
|
13
|
+
* is the union of:
|
|
14
|
+
* - Universal step fields declared on `BaseStepDefinition` in
|
|
15
|
+
* `src/workflows/runner/types.ts`
|
|
16
|
+
* - The top-level fields each step runner in `src/workflows/steps/`
|
|
17
|
+
* actually reads
|
|
18
|
+
*
|
|
19
|
+
* The list is universal-only (not per-kind) by design: the CLI doesn't know
|
|
20
|
+
* which step kinds exist on a given server version, and a per-kind list
|
|
21
|
+
* would couple the CLI tightly to the runtime. A universal allowlist
|
|
22
|
+
* catches the misnested-header footgun cleanly while staying tolerant of
|
|
23
|
+
* future step kinds that consume top-level fields already in the union.
|
|
24
|
+
*
|
|
25
|
+
* Maintenance: when a new step kind starts reading a new top-level field,
|
|
26
|
+
* add the field name to `ALLOWLISTED_FIELDS` below. See `cli/README.md`
|
|
27
|
+
* for the maintenance note.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Read-only view of `ALLOWLISTED_FIELDS` for the drift-guard test (#985).
|
|
31
|
+
*
|
|
32
|
+
* Exported so `cli/tests/unit/workflow-toml-validator-drift-guard.test.ts` can
|
|
33
|
+
* assert this set stays a superset of every top-level field the step runners
|
|
34
|
+
* in `src/workflows/steps/*.ts` actually read — checking the *live* set the
|
|
35
|
+
* validator enforces rather than a copy that could itself drift. Not part of
|
|
36
|
+
* the public validator API; consumed only by the guard.
|
|
37
|
+
*/
|
|
38
|
+
export declare const ALLOWLISTED_FIELDS_FOR_DRIFT_GUARD: ReadonlySet<string>;
|
|
39
|
+
/**
|
|
40
|
+
* A single offending field on a step.
|
|
41
|
+
*
|
|
42
|
+
* `stepIndex` is the array index in `steps[]`.
|
|
43
|
+
* `stepId` is the value of the step's `id` field, if any (string).
|
|
44
|
+
* `field` is the unknown top-level key; uses sentinel names for shape
|
|
45
|
+
* errors:
|
|
46
|
+
* - `__steps_shape__`: `steps` is not an array (table or scalar).
|
|
47
|
+
* - `__step_shape__`: a step is not an object.
|
|
48
|
+
* `hint` is a short, user-facing explanation (used by
|
|
49
|
+
* `formatWorkflowTomlErrors` to render the multi-line diagnostic).
|
|
50
|
+
* `parentStepId` is set when this error came from a `collect` step's
|
|
51
|
+
* nested inner `step` definition (codex review, 2026-05-15): the outer
|
|
52
|
+
* `stepIndex` points at the array index, `parentStepId` carries the
|
|
53
|
+
* outer step's id (if any), and `stepId` carries the inner step's id.
|
|
54
|
+
* It's `null` for top-level errors (the renderer treats both `null` and
|
|
55
|
+
* `undefined` as top-level — see `formatWorkflowTomlErrors`).
|
|
56
|
+
*/
|
|
57
|
+
export type WorkflowTomlError = {
|
|
58
|
+
stepIndex: number;
|
|
59
|
+
stepId: string | null;
|
|
60
|
+
field: string;
|
|
61
|
+
hint: string;
|
|
62
|
+
parentStepId?: string | null;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Walk a parsed workflow TOML document and return all unknown-field errors.
|
|
66
|
+
*
|
|
67
|
+
* @param tomlData The output of `TOML.parse()` for a workflow TOML file.
|
|
68
|
+
* @returns An array of error objects, one per offending field. Empty if no
|
|
69
|
+
* errors were found.
|
|
70
|
+
*/
|
|
71
|
+
export declare function validateWorkflowToml(tomlData: any): WorkflowTomlError[];
|
|
72
|
+
/**
|
|
73
|
+
* Render a multi-line diagnostic for a list of errors. Designed for the
|
|
74
|
+
* shape called out in the issue:
|
|
75
|
+
*
|
|
76
|
+
* Error in workflows/refresh.toml:
|
|
77
|
+
* steps[0] (id="refresh-each") has unknown field "refresh-each".
|
|
78
|
+
* This is usually caused by writing [steps.refresh-each.request] instead of [steps.request].
|
|
79
|
+
* TOML can't address an array element by id — use [steps.request] (refers to the most recent step).
|
|
80
|
+
*
|
|
81
|
+
* @param filePath The TOML file path (or display name) to include in the
|
|
82
|
+
* header.
|
|
83
|
+
* @param errors The error list from `validateWorkflowToml`.
|
|
84
|
+
* @returns A multi-line string ready to print to stderr.
|
|
85
|
+
*/
|
|
86
|
+
export declare function formatWorkflowTomlErrors(filePath: string, errors: WorkflowTomlError[]): string;
|