primitive-admin 1.1.0-alpha.3 → 1.1.0-alpha.31
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 +168 -21
- package/assets/skill/skills/primitive-platform/SKILL.md +226 -0
- package/dist/bin/primitive.js +198 -15
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.js +107 -0
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.js +464 -55
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +27 -13
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.js +165 -5
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +354 -0
- package/dist/src/commands/blob-buckets.js.map +1 -0
- package/dist/src/commands/catalog.js +17 -17
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +178 -0
- package/dist/src/commands/collection-type-configs.js.map +1 -0
- package/dist/src/commands/collections.js +526 -0
- package/dist/src/commands/collections.js.map +1 -0
- package/dist/src/commands/comparisons.js +6 -6
- package/dist/src/commands/comparisons.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +364 -0
- package/dist/src/commands/cron-triggers.js.map +1 -0
- package/dist/src/commands/database-types.js +462 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.js +1067 -58
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +510 -2
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +281 -0
- package/dist/src/commands/email-templates.js.map +1 -0
- package/dist/src/commands/env.js +260 -0
- package/dist/src/commands/env.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +189 -0
- package/dist/src/commands/group-type-configs.js.map +1 -0
- package/dist/src/commands/groups.js +28 -25
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/init.js +719 -188
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +504 -33
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/prompts.js +17 -16
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.js +366 -0
- package/dist/src/commands/rule-sets.js.map +1 -0
- package/dist/src/commands/secrets.js +108 -0
- package/dist/src/commands/secrets.js.map +1 -0
- package/dist/src/commands/skill.js +29 -0
- package/dist/src/commands/skill.js.map +1 -0
- package/dist/src/commands/sync.js +2363 -182
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +9 -9
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +426 -4
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/webhooks.js +386 -0
- package/dist/src/commands/webhooks.js.map +1 -0
- package/dist/src/commands/workflows.js +614 -64
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.js +942 -83
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/config.js +51 -53
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/constants.js +3 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/credentials-store.js +307 -0
- package/dist/src/lib/credentials-store.js.map +1 -0
- package/dist/src/lib/env-resolver.js +121 -0
- package/dist/src/lib/env-resolver.js.map +1 -0
- package/dist/src/lib/init-config.js +87 -0
- package/dist/src/lib/init-config.js.map +1 -0
- package/dist/src/lib/migration-nag.js +163 -0
- package/dist/src/lib/migration-nag.js.map +1 -0
- package/dist/src/lib/output.js +58 -6
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.js +42 -0
- package/dist/src/lib/paginate.js.map +1 -0
- package/dist/src/lib/project-config.js +209 -0
- package/dist/src/lib/project-config.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.js +103 -0
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
- package/dist/src/lib/skill-installer.js +135 -0
- package/dist/src/lib/skill-installer.js.map +1 -0
- package/dist/src/lib/sync-paths.js +102 -0
- package/dist/src/lib/sync-paths.js.map +1 -0
- package/dist/src/lib/template.js +279 -18
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/toml-database-config.js +384 -0
- package/dist/src/lib/toml-database-config.js.map +1 -0
- package/dist/src/lib/toml-params-validator.js +183 -0
- package/dist/src/lib/toml-params-validator.js.map +1 -0
- package/dist/src/lib/version-check.js +172 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/dist/src/lib/workflow-fragments.js +121 -0
- package/dist/src/lib/workflow-fragments.js.map +1 -0
- package/dist/src/lib/workflow-toml-validator.js +328 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
const CONFIG_DIR = process.env.PRIMITIVE_CONFIG_DIR || join(homedir(), ".primitive");
|
|
6
|
+
const VERSION_CHECK_FILE = join(CONFIG_DIR, "version-check.json");
|
|
7
|
+
// Cache the result for 4 hours — short enough that users see new
|
|
8
|
+
// releases the same day they drop, long enough that chatty commands
|
|
9
|
+
// (sync push, tests in loops, etc.) don't hammer the npm registry.
|
|
10
|
+
const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000;
|
|
11
|
+
const NPM_PACKAGE_NAME = "primitive-admin";
|
|
12
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
13
|
+
function ensureConfigDir() {
|
|
14
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
15
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function loadCache() {
|
|
19
|
+
try {
|
|
20
|
+
if (!existsSync(VERSION_CHECK_FILE))
|
|
21
|
+
return {};
|
|
22
|
+
return JSON.parse(readFileSync(VERSION_CHECK_FILE, "utf-8"));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function saveCache(cache) {
|
|
29
|
+
try {
|
|
30
|
+
ensureConfigDir();
|
|
31
|
+
writeFileSync(VERSION_CHECK_FILE, JSON.stringify(cache, null, 2));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Ignore write errors
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extracts the prerelease channel from a version string.
|
|
39
|
+
* "1.0.23-alpha.3" → "alpha"
|
|
40
|
+
* "1.0.23-beta.1" → "beta"
|
|
41
|
+
* "1.0.23" → null
|
|
42
|
+
*/
|
|
43
|
+
function getPrereleaseChannel(version) {
|
|
44
|
+
const match = version.match(/^[^-]+-([a-zA-Z]+)/);
|
|
45
|
+
return match ? match[1] : null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Compares two semver version strings, including prerelease handling.
|
|
49
|
+
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
50
|
+
*
|
|
51
|
+
* Prerelease versions are only compared against their own channel.
|
|
52
|
+
* Within the same channel, prerelease segments are compared numerically.
|
|
53
|
+
*/
|
|
54
|
+
function compareVersions(a, b) {
|
|
55
|
+
const clean = (v) => v.replace(/^v/, "");
|
|
56
|
+
const cleanA = clean(a);
|
|
57
|
+
const cleanB = clean(b);
|
|
58
|
+
// Split into core and prerelease
|
|
59
|
+
const [coreA, preA] = cleanA.split("-", 2);
|
|
60
|
+
const [coreB, preB] = cleanB.split("-", 2);
|
|
61
|
+
// Compare core version (major.minor.patch)
|
|
62
|
+
const partsA = coreA.split(".").map(Number);
|
|
63
|
+
const partsB = coreB.split(".").map(Number);
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
const ca = partsA[i] || 0;
|
|
66
|
+
const cb = partsB[i] || 0;
|
|
67
|
+
if (ca < cb)
|
|
68
|
+
return -1;
|
|
69
|
+
if (ca > cb)
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
// Core versions are equal — compare prerelease
|
|
73
|
+
// No prerelease on either → equal
|
|
74
|
+
if (!preA && !preB)
|
|
75
|
+
return 0;
|
|
76
|
+
// A prerelease version is always less than the release version
|
|
77
|
+
if (preA && !preB)
|
|
78
|
+
return -1;
|
|
79
|
+
if (!preA && preB)
|
|
80
|
+
return 1;
|
|
81
|
+
// Both have prerelease — compare segments (e.g. "alpha.3" vs "alpha.5")
|
|
82
|
+
const segsA = preA.split(".");
|
|
83
|
+
const segsB = preB.split(".");
|
|
84
|
+
const len = Math.max(segsA.length, segsB.length);
|
|
85
|
+
for (let i = 0; i < len; i++) {
|
|
86
|
+
const sa = segsA[i];
|
|
87
|
+
const sb = segsB[i];
|
|
88
|
+
if (sa === undefined)
|
|
89
|
+
return -1;
|
|
90
|
+
if (sb === undefined)
|
|
91
|
+
return 1;
|
|
92
|
+
const na = Number(sa);
|
|
93
|
+
const nb = Number(sb);
|
|
94
|
+
const aIsNum = !isNaN(na);
|
|
95
|
+
const bIsNum = !isNaN(nb);
|
|
96
|
+
if (aIsNum && bIsNum) {
|
|
97
|
+
if (na < nb)
|
|
98
|
+
return -1;
|
|
99
|
+
if (na > nb)
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Lexicographic comparison for non-numeric segments
|
|
104
|
+
if (sa < sb)
|
|
105
|
+
return -1;
|
|
106
|
+
if (sa > sb)
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
async function fetchLatestVersion(distTag) {
|
|
113
|
+
try {
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
116
|
+
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}/${distTag}`, { signal: controller.signal });
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
if (!response.ok)
|
|
119
|
+
return null;
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
return data.version;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Checks if a newer version of the CLI is available on npm.
|
|
129
|
+
*
|
|
130
|
+
* - Stable versions (e.g. "1.0.22") check the "latest" dist-tag
|
|
131
|
+
* - Prerelease versions (e.g. "1.0.23-alpha.3") check their channel's
|
|
132
|
+
* dist-tag (e.g. "alpha"), falling back to "latest" if that tag doesn't exist
|
|
133
|
+
*
|
|
134
|
+
* Caches results per dist-tag for 4 hours. Fails silently on any error.
|
|
135
|
+
*/
|
|
136
|
+
export async function checkForUpdate(currentVersion) {
|
|
137
|
+
try {
|
|
138
|
+
const channel = getPrereleaseChannel(currentVersion);
|
|
139
|
+
const distTag = channel || "latest";
|
|
140
|
+
const cache = loadCache();
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
let latestVersion = null;
|
|
143
|
+
const cached = cache[distTag];
|
|
144
|
+
if (cached && (now - cached.lastCheck) < CHECK_INTERVAL_MS) {
|
|
145
|
+
latestVersion = cached.latestVersion;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
latestVersion = await fetchLatestVersion(distTag);
|
|
149
|
+
// If the prerelease dist-tag doesn't exist on npm, fall back to "latest"
|
|
150
|
+
// so alpha users still hear about new stable releases
|
|
151
|
+
if (!latestVersion && channel) {
|
|
152
|
+
latestVersion = await fetchLatestVersion("latest");
|
|
153
|
+
}
|
|
154
|
+
if (latestVersion) {
|
|
155
|
+
cache[distTag] = { lastCheck: now, latestVersion };
|
|
156
|
+
saveCache(cache);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (latestVersion && compareVersions(currentVersion, latestVersion) < 0) {
|
|
160
|
+
const installCmd = channel
|
|
161
|
+
? `npm i -g ${NPM_PACKAGE_NAME}@${channel}`
|
|
162
|
+
: `npm i -g ${NPM_PACKAGE_NAME}`;
|
|
163
|
+
console.error();
|
|
164
|
+
console.error(chalk.yellow(`Update available: ${chalk.dim(currentVersion)} -> ${chalk.green.bold(latestVersion)}`));
|
|
165
|
+
console.error(chalk.yellow(`Run ${chalk.cyan.bold(installCmd)} to update`));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Never let version check errors affect CLI operation
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=version-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../../src/lib/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AACrF,MAAM,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;AAClE,iEAAiE;AACjE,oEAAoE;AACpE,mEAAmE;AACnE,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC7C,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAC3C,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAU9B,SAAS,eAAe;IACtB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IAChB,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC;YAAE,OAAO,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAwB;IACzC,IAAI,CAAC;QACH,eAAe,EAAE,CAAC;QAClB,aAAa,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,oBAAoB,CAAC,OAAe;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IAC3C,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAExB,iCAAiC;IACjC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAE3C,2CAA2C;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAE5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,+CAA+C;IAC/C,kCAAkC;IAClC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IAC7B,+DAA+D;IAC/D,IAAI,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC;IAE5B,wEAAwE;IACxE,MAAM,KAAK,GAAG,IAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAEjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,CAAC;QAChC,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC;QAE/B,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAE1B,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;YACrB,IAAI,EAAE,GAAG,EAAE;gBAAE,OAAO,CAAC,CAAC,CAAC;YACvB,IAAI,EAAE,GAAG,EAAE;gBAAE,OAAO,CAAC,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,oDAAoD;YACpD,IAAI,EAAE,GAAG,EAAE;gBAAE,OAAO,CAAC,CAAC,CAAC;YACvB,IAAI,EAAE,GAAG,EAAE;gBAAE,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,CAAC;AACX,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,OAAe;IAC/C,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAEvE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,8BAA8B,gBAAgB,IAAI,OAAO,EAAE,EAC3D,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAC9B,CAAC;QACF,YAAY,CAAC,OAAO,CAAC,CAAC;QAEtB,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAyB,CAAC;QAC1D,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,cAAsB;IACzD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,oBAAoB,CAAC,cAAc,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC;QACpC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,aAAa,GAAkB,IAAI,CAAC;QACxC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAE9B,IAAI,MAAM,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,iBAAiB,EAAE,CAAC;YAC3D,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,aAAa,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAElD,yEAAyE;YACzE,sDAAsD;YACtD,IAAI,CAAC,aAAa,IAAI,OAAO,EAAE,CAAC;gBAC9B,aAAa,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YACrD,CAAC;YAED,IAAI,aAAa,EAAE,CAAC;gBAClB,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC;gBACnD,SAAS,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QAED,IAAI,aAAa,IAAI,eAAe,CAAC,cAAc,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YACxE,MAAM,UAAU,GAAG,OAAO;gBACxB,CAAC,CAAC,YAAY,gBAAgB,IAAI,OAAO,EAAE;gBAC3C,CAAC,CAAC,YAAY,gBAAgB,EAAE,CAAC;YAEnC,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,qBAAqB,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;YACpH,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sDAAsD;IACxD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
import { existsSync, readFileSync } from "fs";
|
|
24
|
+
import { dirname, join, basename } from "path";
|
|
25
|
+
import * as TOML from "@iarna/toml";
|
|
26
|
+
/**
|
|
27
|
+
* Read and expand a workflow TOML file from disk. Returns the parsed
|
|
28
|
+
* TOML with all `include` fragments spliced in. If the file has no
|
|
29
|
+
* `include` key, the parsed TOML is returned unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export function expandWorkflow(workflowPath) {
|
|
32
|
+
const content = readFileSync(workflowPath, "utf-8");
|
|
33
|
+
const parsed = TOML.parse(content);
|
|
34
|
+
return expandWorkflowTomlData(parsed, workflowPath);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Expand the `include` key of an already-parsed TOML object. Resolves
|
|
38
|
+
* fragment files relative to `<workflowPath>/../../workflow-fragments/`.
|
|
39
|
+
* Use this when the TOML has already been parsed elsewhere (e.g. inside
|
|
40
|
+
* `parseTomlFile()`).
|
|
41
|
+
*/
|
|
42
|
+
export function expandWorkflowTomlData(parsed, workflowPath) {
|
|
43
|
+
// Fast path: nothing to expand. Preserve the original shape exactly —
|
|
44
|
+
// including the absence of a `steps` field, since not every TOML file
|
|
45
|
+
// routed through `parseTomlFile()` represents a workflow.
|
|
46
|
+
if (!("include" in parsed)) {
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
const includeList = parsed.include;
|
|
50
|
+
if (!Array.isArray(includeList)) {
|
|
51
|
+
throw new Error(`Workflow '${workflowPath}' has an 'include' key but it is not an array. Use \`include = ["fragment-name"]\`.`);
|
|
52
|
+
}
|
|
53
|
+
// Resolve fragments directory: sibling of workflow's parent directory.
|
|
54
|
+
// E.g. workflow at config/workflows/foo.toml
|
|
55
|
+
// fragments at config/workflow-fragments/<name>.toml
|
|
56
|
+
const fragmentsDir = join(dirname(workflowPath), "..", "workflow-fragments");
|
|
57
|
+
const workflowOwnSteps = Array.isArray(parsed.steps)
|
|
58
|
+
? parsed.steps
|
|
59
|
+
: [];
|
|
60
|
+
const allOriginated = [];
|
|
61
|
+
for (const fragmentName of includeList) {
|
|
62
|
+
if (typeof fragmentName !== "string" || fragmentName.length === 0) {
|
|
63
|
+
throw new Error(`Workflow '${workflowPath}' has an invalid include entry: ${JSON.stringify(fragmentName)}. Each entry must be a fragment name string.`);
|
|
64
|
+
}
|
|
65
|
+
const fragmentPath = join(fragmentsDir, `${fragmentName}.toml`);
|
|
66
|
+
const fragment = readFragmentSafely(fragmentPath, workflowPath, fragmentName);
|
|
67
|
+
for (const step of fragment.steps) {
|
|
68
|
+
allOriginated.push({ step, origin: `fragment '${fragmentName}'` });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const step of workflowOwnSteps) {
|
|
72
|
+
allOriginated.push({ step, origin: `workflow '${basename(workflowPath)}'` });
|
|
73
|
+
}
|
|
74
|
+
assertUniqueStepIds(allOriginated, workflowPath);
|
|
75
|
+
// Build the expanded object: drop `include`, replace `steps` with the
|
|
76
|
+
// concatenated list. Preserve all other top-level keys (workflow,
|
|
77
|
+
// triggers, configs, etc.) exactly as parsed.
|
|
78
|
+
const result = { ...parsed };
|
|
79
|
+
delete result.include;
|
|
80
|
+
result.steps = allOriginated.map((o) => o.step);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
function readFragmentSafely(fragmentPath, workflowPath, fragmentName) {
|
|
84
|
+
if (!existsSync(fragmentPath)) {
|
|
85
|
+
throw new Error(`Workflow '${workflowPath}' includes fragment '${fragmentName}' but the file does not exist at '${fragmentPath}'.`);
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
const content = readFileSync(fragmentPath, "utf-8");
|
|
90
|
+
parsed = TOML.parse(content);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
throw new Error(`Failed to parse fragment '${fragmentName}' at '${fragmentPath}': ${err?.message ?? err}`);
|
|
94
|
+
}
|
|
95
|
+
if ("include" in parsed) {
|
|
96
|
+
throw new Error(`Fragment '${fragmentName}' at '${fragmentPath}' contains its own 'include' key — recursive includes are not supported in v1. Inline the steps directly into this fragment.`);
|
|
97
|
+
}
|
|
98
|
+
if ("workflow" in parsed) {
|
|
99
|
+
throw new Error(`Fragment '${fragmentName}' at '${fragmentPath}' contains a [workflow] block. Fragment files must be [[steps]] lists only.`);
|
|
100
|
+
}
|
|
101
|
+
const steps = Array.isArray(parsed.steps)
|
|
102
|
+
? parsed.steps
|
|
103
|
+
: [];
|
|
104
|
+
return { steps };
|
|
105
|
+
}
|
|
106
|
+
function assertUniqueStepIds(originated, workflowPath) {
|
|
107
|
+
const seen = new Map();
|
|
108
|
+
for (const { step, origin } of originated) {
|
|
109
|
+
const id = step?.id;
|
|
110
|
+
if (id === undefined || id === null)
|
|
111
|
+
continue; // let downstream code complain about missing ids
|
|
112
|
+
if (typeof id !== "string")
|
|
113
|
+
continue;
|
|
114
|
+
if (seen.has(id)) {
|
|
115
|
+
const firstOrigin = seen.get(id);
|
|
116
|
+
throw new Error(`Step id '${id}' appears twice when expanding workflow '${workflowPath}': first in ${firstOrigin}, then in ${origin}.`);
|
|
117
|
+
}
|
|
118
|
+
seen.set(id, origin);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=workflow-fragments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-fragments.js","sourceRoot":"","sources":["../../../src/lib/workflow-fragments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,328 @@
|
|
|
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
|
+
* Universal-only allowlist of top-level fields permitted on a step.
|
|
31
|
+
*
|
|
32
|
+
* Membership combines:
|
|
33
|
+
* 1) Universal fields on `BaseStepDefinition` in
|
|
34
|
+
* `src/workflows/runner/types.ts` and the runner engine's reads of
|
|
35
|
+
* `stepDef.*`.
|
|
36
|
+
* 2) The union of top-level fields actually consumed by step runners in
|
|
37
|
+
* `src/workflows/steps/` (audited 2026-05-15).
|
|
38
|
+
*
|
|
39
|
+
* Keep this sorted to make diffs reviewable.
|
|
40
|
+
*/
|
|
41
|
+
const ALLOWLISTED_FIELDS = new Set([
|
|
42
|
+
// Universal (BaseStepDefinition + engine).
|
|
43
|
+
"as",
|
|
44
|
+
"concurrency",
|
|
45
|
+
"continueOnError",
|
|
46
|
+
"description",
|
|
47
|
+
"forEach",
|
|
48
|
+
"id",
|
|
49
|
+
"kind",
|
|
50
|
+
"maxItems",
|
|
51
|
+
"name",
|
|
52
|
+
"runIf",
|
|
53
|
+
"saveAs",
|
|
54
|
+
"selector",
|
|
55
|
+
"strict",
|
|
56
|
+
"successWhen",
|
|
57
|
+
"timeout",
|
|
58
|
+
// Per-kind, union across runners.
|
|
59
|
+
"_testSteps", // workflow.call test-mode override
|
|
60
|
+
"action",
|
|
61
|
+
"appId",
|
|
62
|
+
"asBase64",
|
|
63
|
+
"attachments",
|
|
64
|
+
"blobId",
|
|
65
|
+
"bodyMode",
|
|
66
|
+
"bucketId",
|
|
67
|
+
"bucketKey",
|
|
68
|
+
"cacheTtlSeconds",
|
|
69
|
+
"configId",
|
|
70
|
+
"content",
|
|
71
|
+
"contentBase64",
|
|
72
|
+
"contentType",
|
|
73
|
+
"context",
|
|
74
|
+
"cursor",
|
|
75
|
+
"cursorField",
|
|
76
|
+
"databaseId",
|
|
77
|
+
"direction",
|
|
78
|
+
"dryRun",
|
|
79
|
+
"durationMs",
|
|
80
|
+
"email",
|
|
81
|
+
"events",
|
|
82
|
+
"expiresInSeconds",
|
|
83
|
+
"feature",
|
|
84
|
+
"filename",
|
|
85
|
+
"filters",
|
|
86
|
+
"groupBy",
|
|
87
|
+
"groupId",
|
|
88
|
+
"groupType",
|
|
89
|
+
"htmlBody",
|
|
90
|
+
"includeUserDetails",
|
|
91
|
+
"input",
|
|
92
|
+
"integrationKey",
|
|
93
|
+
"itemsField",
|
|
94
|
+
"limit",
|
|
95
|
+
"maxPages",
|
|
96
|
+
"message",
|
|
97
|
+
"messages",
|
|
98
|
+
"metrics",
|
|
99
|
+
"model",
|
|
100
|
+
"modelOverride",
|
|
101
|
+
"ms",
|
|
102
|
+
"multipartFields",
|
|
103
|
+
"onPartialFailure",
|
|
104
|
+
"operationName",
|
|
105
|
+
"output",
|
|
106
|
+
"page",
|
|
107
|
+
"params",
|
|
108
|
+
"payload",
|
|
109
|
+
"plugins",
|
|
110
|
+
"prompt",
|
|
111
|
+
"promptKey",
|
|
112
|
+
"query",
|
|
113
|
+
"queryType",
|
|
114
|
+
"request",
|
|
115
|
+
"route",
|
|
116
|
+
"runs",
|
|
117
|
+
"sort",
|
|
118
|
+
"step", // collect: nested inner step definition
|
|
119
|
+
"subject",
|
|
120
|
+
"tags",
|
|
121
|
+
"temperature",
|
|
122
|
+
"templateType",
|
|
123
|
+
"textBody",
|
|
124
|
+
"thinkingLevel",
|
|
125
|
+
"to",
|
|
126
|
+
"tools",
|
|
127
|
+
"tool_choice",
|
|
128
|
+
"top_p",
|
|
129
|
+
"toUserId",
|
|
130
|
+
"type",
|
|
131
|
+
"user",
|
|
132
|
+
"userId",
|
|
133
|
+
"userUlid",
|
|
134
|
+
"variables",
|
|
135
|
+
"windowDays",
|
|
136
|
+
"workflowKey",
|
|
137
|
+
]);
|
|
138
|
+
/**
|
|
139
|
+
* Walk a parsed workflow TOML document and return all unknown-field errors.
|
|
140
|
+
*
|
|
141
|
+
* @param tomlData The output of `TOML.parse()` for a workflow TOML file.
|
|
142
|
+
* @returns An array of error objects, one per offending field. Empty if no
|
|
143
|
+
* errors were found.
|
|
144
|
+
*/
|
|
145
|
+
export function validateWorkflowToml(tomlData) {
|
|
146
|
+
const errors = [];
|
|
147
|
+
if (tomlData === null || tomlData === undefined) {
|
|
148
|
+
return errors;
|
|
149
|
+
}
|
|
150
|
+
const steps = tomlData.steps;
|
|
151
|
+
// No steps section at all → nothing to validate. This is fine for
|
|
152
|
+
// partial TOML files (some commands accept a metadata-only file).
|
|
153
|
+
if (steps === undefined || steps === null) {
|
|
154
|
+
return errors;
|
|
155
|
+
}
|
|
156
|
+
// `steps` is a table instead of an array. This typically means the user
|
|
157
|
+
// wrote `[steps.foo]` with no preceding `[[steps]]` array marker, so
|
|
158
|
+
// TOML resolved `steps` to a table. The runtime expects an array — flag
|
|
159
|
+
// it with a clear shape diagnostic.
|
|
160
|
+
if (!Array.isArray(steps)) {
|
|
161
|
+
if (typeof steps === "object") {
|
|
162
|
+
errors.push({
|
|
163
|
+
stepIndex: -1,
|
|
164
|
+
stepId: null,
|
|
165
|
+
field: "__steps_shape__",
|
|
166
|
+
hint: 'The `steps` section is a table, not an array. Workflow TOML expects `[[steps]]` array markers — add `[[steps]]` before any `[steps.<field>]` blocks.',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
errors.push({
|
|
171
|
+
stepIndex: -1,
|
|
172
|
+
stepId: null,
|
|
173
|
+
field: "__steps_shape__",
|
|
174
|
+
hint: `The \`steps\` section must be an array of step tables (received: ${typeof steps}).`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return errors;
|
|
178
|
+
}
|
|
179
|
+
for (let i = 0; i < steps.length; i++) {
|
|
180
|
+
const step = steps[i];
|
|
181
|
+
// Defensive: array entry isn't an object. Should be near-impossible with
|
|
182
|
+
// valid `[[steps]]` syntax, but possible if the user writes `steps =
|
|
183
|
+
// ["a", "b"]`. Surface it so the user gets a diagnostic instead of a
|
|
184
|
+
// silent skip.
|
|
185
|
+
if (step === null || typeof step !== "object" || Array.isArray(step)) {
|
|
186
|
+
errors.push({
|
|
187
|
+
stepIndex: i,
|
|
188
|
+
stepId: null,
|
|
189
|
+
field: "__step_shape__",
|
|
190
|
+
hint: `Each entry in \`steps[]\` must be a table. Got: ${step === null ? "null" : Array.isArray(step) ? "array" : typeof step}.`,
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
validateStepObject(step, i, /* parentStepId */ null, errors);
|
|
195
|
+
// Codex finding 2 (PR #762, 2026-05-15): a `collect` step's nested
|
|
196
|
+
// `step` field is itself a step definition that the collect runner
|
|
197
|
+
// executes. The outer-step allowlist accepts `step` as a top-level
|
|
198
|
+
// field, but that's a structural pass-through — we need to recurse
|
|
199
|
+
// into the inner step's keys to catch the same misnested-header
|
|
200
|
+
// footgun (e.g. `[steps.step.call.request]` parses as `step.step =
|
|
201
|
+
// { call: { request: {...} } }`, with `call` as an unknown inner
|
|
202
|
+
// field; without the recursion this slips through). We only recurse
|
|
203
|
+
// for `kind === "collect"` — other kinds with a `step`-named field
|
|
204
|
+
// (none today) would need their own audit.
|
|
205
|
+
if (step.kind === "collect" &&
|
|
206
|
+
step.step !== null &&
|
|
207
|
+
typeof step.step === "object" &&
|
|
208
|
+
!Array.isArray(step.step)) {
|
|
209
|
+
const outerStepId = typeof step.id === "string" ? step.id : null;
|
|
210
|
+
validateStepObject(step.step, i, outerStepId, errors);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return errors;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Validate a single step object's top-level keys against the allowlist and
|
|
217
|
+
* push any errors found onto `errors`. Used for both the top-level
|
|
218
|
+
* `steps[]` walk and the recursive `collect.step` walk.
|
|
219
|
+
*
|
|
220
|
+
* When `parentStepId` is set, the step being validated is the inner step
|
|
221
|
+
* of a `collect`. Errors carry the outer-step context so the rendered
|
|
222
|
+
* diagnostic can point operators at the right slot in the TOML.
|
|
223
|
+
*/
|
|
224
|
+
function validateStepObject(step, stepIndex, parentStepId, errors) {
|
|
225
|
+
const stepId = typeof step.id === "string" ? step.id : null;
|
|
226
|
+
for (const key of Object.keys(step)) {
|
|
227
|
+
const value = step[key];
|
|
228
|
+
// Codex finding 1 (PR #762, 2026-05-15): self-id misnesting must be
|
|
229
|
+
// checked BEFORE the allowlist skip. When the step's `id` happens to
|
|
230
|
+
// match an allowlisted field name (e.g. `id = "query"`,
|
|
231
|
+
// `id = "request"`, `id = "input"`, `id = "output"`), TOML's
|
|
232
|
+
// `[steps.<id>.<sub-field>]` misnest produces a sub-table on the step
|
|
233
|
+
// keyed by the id — the same shape the validator was meant to catch.
|
|
234
|
+
// The allowlist would otherwise skip it as "expected per-kind field".
|
|
235
|
+
// Heuristic: when `step[step.id]` is a non-array table, this is
|
|
236
|
+
// (effectively always) the misnest pattern. Scalars are fine (the
|
|
237
|
+
// user really did mean to set the scalar field).
|
|
238
|
+
const isSelfIdMisnest = stepId !== null &&
|
|
239
|
+
key === stepId &&
|
|
240
|
+
value !== null &&
|
|
241
|
+
typeof value === "object" &&
|
|
242
|
+
!Array.isArray(value);
|
|
243
|
+
if (!isSelfIdMisnest && ALLOWLISTED_FIELDS.has(key))
|
|
244
|
+
continue;
|
|
245
|
+
// Two failure modes are common enough to call out specifically in
|
|
246
|
+
// the hint:
|
|
247
|
+
//
|
|
248
|
+
// 1) `[steps.<id>.<field>]` under `[[steps]]` — TOML places the
|
|
249
|
+
// sub-table under `steps[N][<id>]`. We detect this when the
|
|
250
|
+
// unknown field name equals the step's own id (including the
|
|
251
|
+
// self-id-allowlist-collision case above). We use the first
|
|
252
|
+
// sub-key of the offending value (e.g. `request`) to build a
|
|
253
|
+
// concrete corrected-form example so the user sees exactly what
|
|
254
|
+
// to rewrite, not just an abstract `<field>`.
|
|
255
|
+
//
|
|
256
|
+
// 2) Any other unknown top-level field — covered by the generic hint.
|
|
257
|
+
let hint;
|
|
258
|
+
if (stepId !== null && key === stepId) {
|
|
259
|
+
const subKey = value && typeof value === "object" && !Array.isArray(value)
|
|
260
|
+
? Object.keys(value)[0]
|
|
261
|
+
: null;
|
|
262
|
+
const exampleSub = subKey ?? "request";
|
|
263
|
+
hint =
|
|
264
|
+
`This is usually caused by writing [steps.${stepId}.${exampleSub}] instead of [steps.${exampleSub}]. ` +
|
|
265
|
+
`TOML can't address an array element by id — use [steps.${exampleSub}] (refers to the most recent step).`;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
hint =
|
|
269
|
+
`\`${key}\` is not a recognized step field. ` +
|
|
270
|
+
`If you meant to write a nested config block on this step, use [steps.${key}] (which refers to the most recent step) instead of [steps.<id>.${key}].`;
|
|
271
|
+
}
|
|
272
|
+
errors.push({
|
|
273
|
+
stepIndex,
|
|
274
|
+
stepId,
|
|
275
|
+
field: key,
|
|
276
|
+
hint,
|
|
277
|
+
parentStepId,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Render a multi-line diagnostic for a list of errors. Designed for the
|
|
283
|
+
* shape called out in the issue:
|
|
284
|
+
*
|
|
285
|
+
* Error in workflows/refresh.toml:
|
|
286
|
+
* steps[0] (id="refresh-each") has unknown field "refresh-each".
|
|
287
|
+
* This is usually caused by writing [steps.refresh-each.request] instead of [steps.request].
|
|
288
|
+
* TOML can't address an array element by id — use [steps.request] (refers to the most recent step).
|
|
289
|
+
*
|
|
290
|
+
* @param filePath The TOML file path (or display name) to include in the
|
|
291
|
+
* header.
|
|
292
|
+
* @param errors The error list from `validateWorkflowToml`.
|
|
293
|
+
* @returns A multi-line string ready to print to stderr.
|
|
294
|
+
*/
|
|
295
|
+
export function formatWorkflowTomlErrors(filePath, errors) {
|
|
296
|
+
if (errors.length === 0)
|
|
297
|
+
return "";
|
|
298
|
+
const lines = [`Error in ${filePath}:`];
|
|
299
|
+
for (const err of errors) {
|
|
300
|
+
if (err.field === "__steps_shape__") {
|
|
301
|
+
lines.push(` ${err.hint}`);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (err.field === "__step_shape__") {
|
|
305
|
+
lines.push(` steps[${err.stepIndex}] is malformed: ${err.hint}`);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// For errors inside a `collect` step's nested inner step (codex review,
|
|
309
|
+
// 2026-05-15), use a `steps[N].step` slot prefix so operators see that
|
|
310
|
+
// the offending field is on the inner step, not the outer collect.
|
|
311
|
+
// The outer step's id (if any) is also included in the prefix so the
|
|
312
|
+
// diagnostic can be cross-referenced against the parent's TOML header.
|
|
313
|
+
const isInnerStep = err.parentStepId !== undefined;
|
|
314
|
+
const slot = isInnerStep
|
|
315
|
+
? `steps[${err.stepIndex}].step`
|
|
316
|
+
: `steps[${err.stepIndex}]`;
|
|
317
|
+
const idPart = err.stepId ? ` (id="${err.stepId}")` : "";
|
|
318
|
+
const parentPart = isInnerStep && err.parentStepId
|
|
319
|
+
? ` (inside collect step id="${err.parentStepId}")`
|
|
320
|
+
: isInnerStep
|
|
321
|
+
? ` (inside collect step)`
|
|
322
|
+
: "";
|
|
323
|
+
lines.push(` ${slot}${idPart}${parentPart} has unknown field "${err.field}".`);
|
|
324
|
+
lines.push(` ${err.hint}`);
|
|
325
|
+
}
|
|
326
|
+
return lines.join("\n");
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=workflow-toml-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-toml-validator.js","sourceRoot":"","sources":["../../../src/lib/workflow-toml-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAS;IACzC,2CAA2C;IAC3C,IAAI;IACJ,aAAa;IACb,iBAAiB;IACjB,aAAa;IACb,SAAS;IACT,IAAI;IACJ,MAAM;IACN,UAAU;IACV,MAAM;IACN,OAAO;IACP,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,aAAa;IACb,SAAS;IACT,kCAAkC;IAClC,YAAY,EAAE,mCAAmC;IACjD,QAAQ;IACR,OAAO;IACP,UAAU;IACV,aAAa;IACb,QAAQ;IACR,UAAU;IACV,UAAU;IACV,WAAW;IACX,iBAAiB;IACjB,UAAU;IACV,SAAS;IACT,eAAe;IACf,aAAa;IACb,SAAS;IACT,QAAQ;IACR,aAAa;IACb,YAAY;IACZ,WAAW;IACX,QAAQ;IACR,YAAY;IACZ,OAAO;IACP,QAAQ;IACR,kBAAkB;IAClB,SAAS;IACT,UAAU;IACV,SAAS;IACT,SAAS;IACT,SAAS;IACT,WAAW;IACX,UAAU;IACV,oBAAoB;IACpB,OAAO;IACP,gBAAgB;IAChB,YAAY;IACZ,OAAO;IACP,UAAU;IACV,SAAS;IACT,UAAU;IACV,SAAS;IACT,OAAO;IACP,eAAe;IACf,IAAI;IACJ,iBAAiB;IACjB,kBAAkB;IAClB,eAAe;IACf,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,SAAS;IACT,SAAS;IACT,QAAQ;IACR,WAAW;IACX,OAAO;IACP,WAAW;IACX,SAAS;IACT,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM,EAAE,wCAAwC;IAChD,SAAS;IACT,MAAM;IACN,aAAa;IACb,cAAc;IACd,UAAU;IACV,eAAe;IACf,IAAI;IACJ,OAAO;IACP,aAAa;IACb,OAAO;IACP,UAAU;IACV,MAAM;IACN,MAAM;IACN,QAAQ;IACR,UAAU;IACV,WAAW;IACX,YAAY;IACZ,aAAa;CACd,CAAC,CAAC;AA2BH;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAa;IAChD,MAAM,MAAM,GAAwB,EAAE,CAAC;IAEvC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAE7B,kEAAkE;IAClE,kEAAkE;IAClE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,wEAAwE;IACxE,qEAAqE;IACrE,wEAAwE;IACxE,oCAAoC;IACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC;gBACV,SAAS,EAAE,CAAC,CAAC;gBACb,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,iBAAiB;gBACxB,IAAI,EACF,sJAAsJ;aACzJ,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC;gBACV,SAAS,EAAE,CAAC,CAAC;gBACb,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,iBAAiB;gBACxB,IAAI,EAAE,oEAAoE,OAAO,KAAK,IAAI;aAC3F,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAEtB,yEAAyE;QACzE,qEAAqE;QACrE,qEAAqE;QACrE,eAAe;QACf,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC;gBACV,SAAS,EAAE,CAAC;gBACZ,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,gBAAgB;gBACvB,IAAI,EAAE,mDACJ,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAClE,GAAG;aACJ,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,kBAAkB,CAAC,IAAI,EAAE,CAAC,EAAE,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAE7D,mEAAmE;QACnE,mEAAmE;QACnE,mEAAmE;QACnE,mEAAmE;QACnE,gEAAgE;QAChE,mEAAmE;QACnE,iEAAiE;QACjE,oEAAoE;QACpE,mEAAmE;QACnE,2CAA2C;QAC3C,IACE,IAAI,CAAC,IAAI,KAAK,SAAS;YACvB,IAAI,CAAC,IAAI,KAAK,IAAI;YAClB,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EACzB,CAAC;YACD,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACjE,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CACzB,IAAS,EACT,SAAiB,EACjB,YAA2B,EAC3B,MAA2B;IAE3B,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAE5D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAExB,oEAAoE;QACpE,qEAAqE;QACrE,wDAAwD;QACxD,6DAA6D;QAC7D,sEAAsE;QACtE,qEAAqE;QACrE,sEAAsE;QACtE,gEAAgE;QAChE,kEAAkE;QAClE,iDAAiD;QACjD,MAAM,eAAe,GACnB,MAAM,KAAK,IAAI;YACf,GAAG,KAAK,MAAM;YACd,KAAK,KAAK,IAAI;YACd,OAAO,KAAK,KAAK,QAAQ;YACzB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAExB,IAAI,CAAC,eAAe,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAE9D,kEAAkE;QAClE,YAAY;QACZ,EAAE;QACF,gEAAgE;QAChE,+DAA+D;QAC/D,gEAAgE;QAChE,+DAA+D;QAC/D,gEAAgE;QAChE,mEAAmE;QACnE,iDAAiD;QACjD,EAAE;QACF,sEAAsE;QACtE,IAAI,IAAY,CAAC;QACjB,IAAI,MAAM,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACtC,MAAM,MAAM,GACV,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBACzD,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACvB,CAAC,CAAC,IAAI,CAAC;YACX,MAAM,UAAU,GAAG,MAAM,IAAI,SAAS,CAAC;YACvC,IAAI;gBACF,4CAA4C,MAAM,IAAI,UAAU,uBAAuB,UAAU,KAAK;oBACtG,0DAA0D,UAAU,qCAAqC,CAAC;QAC9G,CAAC;aAAM,CAAC;YACN,IAAI;gBACF,KAAK,GAAG,qCAAqC;oBAC7C,wEAAwE,GAAG,mEAAmE,GAAG,IAAI,CAAC;QAC1J,CAAC;QAED,MAAM,CAAC,IAAI,CAAC;YACV,SAAS;YACT,MAAM;YACN,KAAK,EAAE,GAAG;YACV,IAAI;YACJ,YAAY;SACb,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAgB,EAChB,MAA2B;IAE3B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAa,CAAC,YAAY,QAAQ,GAAG,CAAC,CAAC;IAElD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,GAAG,CAAC,KAAK,KAAK,iBAAiB,EAAE,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC5B,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,KAAK,KAAK,gBAAgB,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,SAAS,mBAAmB,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAClE,SAAS;QACX,CAAC;QAED,wEAAwE;QACxE,uEAAuE;QACvE,mEAAmE;QACnE,qEAAqE;QACrE,uEAAuE;QACvE,MAAM,WAAW,GAAG,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC;QACnD,MAAM,IAAI,GAAG,WAAW;YACtB,CAAC,CAAC,SAAS,GAAG,CAAC,SAAS,QAAQ;YAChC,CAAC,CAAC,SAAS,GAAG,CAAC,SAAS,GAAG,CAAC;QAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,UAAU,GACd,WAAW,IAAI,GAAG,CAAC,YAAY;YAC7B,CAAC,CAAC,6BAA6B,GAAG,CAAC,YAAY,IAAI;YACnD,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,wBAAwB;gBAC1B,CAAC,CAAC,EAAE,CAAC;QACT,KAAK,CAAC,IAAI,CACR,KAAK,IAAI,GAAG,MAAM,GAAG,UAAU,uBAAuB,GAAG,CAAC,KAAK,IAAI,CACpE,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|