primitive-admin 1.0.54 → 1.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/primitive.js +2 -0
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/apps.js +54 -2
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/databases.js +31 -10
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +77 -0
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/settings.d.ts +15 -0
- package/dist/src/commands/settings.js +102 -0
- package/dist/src/commands/settings.js.map +1 -0
- package/dist/src/commands/sync-app-settings.d.ts +105 -0
- package/dist/src/commands/sync-app-settings.js +339 -0
- package/dist/src/commands/sync-app-settings.js.map +1 -0
- package/dist/src/commands/sync.d.ts +34 -19
- package/dist/src/commands/sync.js +373 -200
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +32 -10
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +2 -0
- package/dist/src/lib/api-client.js +6 -0
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
- package/dist/src/lib/app-settings-descriptor.js +250 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +15 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js +27 -10
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +11 -83
- package/dist/src/lib/db-codegen/dbGenerator.js +93 -556
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +15 -7
- package/dist/src/lib/db-codegen/dbNaming.js +25 -13
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +55 -4
- package/dist/src/lib/db-codegen/dbTemplates.js +169 -40
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +101 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +200 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +68 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js +380 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
- package/dist/src/lib/swift-codegen/generator.d.ts +84 -0
- package/dist/src/lib/swift-codegen/generator.js +178 -0
- package/dist/src/lib/swift-codegen/generator.js.map +1 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
- package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
- package/dist/src/lib/sync-resource-types.d.ts +225 -0
- package/dist/src/lib/sync-resource-types.js +394 -0
- package/dist/src/lib/sync-resource-types.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generator.d.ts +18 -5
- package/dist/src/lib/workflow-codegen/generator.js +116 -41
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +85 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared layout metadata for the per-entity resource types `sync pull` writes
|
|
3
|
+
* into a sync slot (issue #1659).
|
|
4
|
+
*
|
|
5
|
+
* `sync pull` writes one file per server entity and records what it wrote in
|
|
6
|
+
* `.primitive-sync.json` (`SyncState.entities[<slot>]`). Until #1659 it never
|
|
7
|
+
* removed a local file whose entity had disappeared server-side, so the next
|
|
8
|
+
* `sync push` — which treats local TOML as the source of truth — silently
|
|
9
|
+
* re-created the just-deleted entity.
|
|
10
|
+
*
|
|
11
|
+
* This module holds the narrow descriptor the prune pass and `sync diff` share:
|
|
12
|
+
* directory, extension, state slot, key→filename derivation, and the sidecar
|
|
13
|
+
* test-directory block type. The pull write loops deliberately stay as they are
|
|
14
|
+
* — this table is layout metadata, not a write framework.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, rmSync, statSync, unlinkSync } from "fs";
|
|
17
|
+
import { dirname, join } from "path";
|
|
18
|
+
const tomlFile = (key) => `${key}.toml`;
|
|
19
|
+
/**
|
|
20
|
+
* Rule sets are keyed on disk (and in sync state) by their name with
|
|
21
|
+
* filesystem-hostile characters replaced. Shared by the pull write loop and the
|
|
22
|
+
* prune pass so the two can never derive a different key for the same rule set.
|
|
23
|
+
*/
|
|
24
|
+
export function ruleSetFileKey(ruleSet) {
|
|
25
|
+
return String(ruleSet.name || ruleSet.ruleSetId || "").replace(/[/\\:*?"<>|]/g, "_");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Every per-entity resource type in a sync slot. `app.toml` and `vars.toml` are
|
|
29
|
+
* root singletons, not per-entity directories, so they are not listed here —
|
|
30
|
+
* var removal is already handled within-file by `sync push` (`planVarsPush`).
|
|
31
|
+
*/
|
|
32
|
+
export const SYNC_RESOURCE_TYPES = [
|
|
33
|
+
{
|
|
34
|
+
label: "integration",
|
|
35
|
+
dir: "integrations",
|
|
36
|
+
ext: ".toml",
|
|
37
|
+
stateSlot: "integrations",
|
|
38
|
+
fileFromKey: tomlFile,
|
|
39
|
+
// `/admin/api/apps/{id}/integrations` paginates; pull drains it with fetchAll.
|
|
40
|
+
listingComplete: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: "webhook",
|
|
44
|
+
dir: "webhooks",
|
|
45
|
+
ext: ".toml",
|
|
46
|
+
stateSlot: "webhooks",
|
|
47
|
+
fileFromKey: tomlFile,
|
|
48
|
+
// `/admin/api/apps/{id}/webhooks` paginates; pull drains it with fetchAll.
|
|
49
|
+
listingComplete: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: "cron-trigger",
|
|
53
|
+
dir: "cron-triggers",
|
|
54
|
+
ext: ".toml",
|
|
55
|
+
stateSlot: "cronTriggers",
|
|
56
|
+
fileFromKey: tomlFile,
|
|
57
|
+
// `{ limit: 100 }`, no cursor, archived filtered after the query.
|
|
58
|
+
listingComplete: false,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: "blob-bucket",
|
|
62
|
+
dir: "blob-buckets",
|
|
63
|
+
ext: ".toml",
|
|
64
|
+
stateSlot: "blobBuckets",
|
|
65
|
+
fileFromKey: tomlFile,
|
|
66
|
+
// `queryByIndex("bucketsForApp")` — no cursor, one page.
|
|
67
|
+
listingComplete: false,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
label: "prompt",
|
|
71
|
+
dir: "prompts",
|
|
72
|
+
ext: ".toml",
|
|
73
|
+
stateSlot: "prompts",
|
|
74
|
+
fileFromKey: tomlFile,
|
|
75
|
+
testBlockType: "prompt",
|
|
76
|
+
// `/admin/api/apps/{id}/prompts` paginates; pull drains it with fetchAll.
|
|
77
|
+
listingComplete: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: "workflow",
|
|
81
|
+
dir: "workflows",
|
|
82
|
+
ext: ".toml",
|
|
83
|
+
stateSlot: "workflows",
|
|
84
|
+
fileFromKey: tomlFile,
|
|
85
|
+
testBlockType: "workflow",
|
|
86
|
+
// `/admin/api/apps/{id}/workflows` paginates; pull drains it with fetchAll.
|
|
87
|
+
listingComplete: true,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
// Rhai scripts: `.rhai` bodies, not TOML, with tests at
|
|
91
|
+
// `transforms/<name>.tests/`.
|
|
92
|
+
label: "transform",
|
|
93
|
+
dir: "transforms",
|
|
94
|
+
ext: ".rhai",
|
|
95
|
+
stateSlot: "scripts",
|
|
96
|
+
fileFromKey: (key) => `${key}.rhai`,
|
|
97
|
+
testBlockType: "script",
|
|
98
|
+
// `queryByIndex("scriptsByApp")` — no cursor, one page.
|
|
99
|
+
listingComplete: false,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
label: "database-type",
|
|
103
|
+
dir: "database-types",
|
|
104
|
+
ext: ".toml",
|
|
105
|
+
stateSlot: "databaseTypes",
|
|
106
|
+
fileFromKey: tomlFile,
|
|
107
|
+
// `queryByIndex("databaseTypeConfigsForApp")` — no cursor, one page.
|
|
108
|
+
listingComplete: false,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
// The state key is already the sanitized filename the write loop derives
|
|
112
|
+
// from the rule set's name (`name.replace(/[/\\:*?"<>|]/g, "_")`), so the
|
|
113
|
+
// filename is the key plus the extension.
|
|
114
|
+
label: "rule-set",
|
|
115
|
+
dir: "rule-sets",
|
|
116
|
+
ext: ".toml",
|
|
117
|
+
stateSlot: "ruleSets",
|
|
118
|
+
fileFromKey: tomlFile,
|
|
119
|
+
// `queryByIndex("ruleSetsForApp")` — no cursor, one page.
|
|
120
|
+
listingComplete: false,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: "group-type-config",
|
|
124
|
+
dir: "group-type-configs",
|
|
125
|
+
ext: ".toml",
|
|
126
|
+
stateSlot: "groupTypeConfigs",
|
|
127
|
+
fileFromKey: tomlFile,
|
|
128
|
+
// `queryByIndex("configsForApp")` — no cursor, one page.
|
|
129
|
+
listingComplete: false,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: "collection-type-config",
|
|
133
|
+
dir: "collection-type-configs",
|
|
134
|
+
ext: ".toml",
|
|
135
|
+
stateSlot: "collectionTypeConfigs",
|
|
136
|
+
fileFromKey: tomlFile,
|
|
137
|
+
// `queryByIndex("collectionConfigsForApp")` — no cursor, one page.
|
|
138
|
+
listingComplete: false,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
// State key is the `resourceType#category` pair; the file on disk is
|
|
142
|
+
// `resourceType.category.toml`.
|
|
143
|
+
label: "metadata-category-config",
|
|
144
|
+
dir: "metadata-category-configs",
|
|
145
|
+
ext: ".toml",
|
|
146
|
+
stateSlot: "metadataCategoryConfigs",
|
|
147
|
+
fileFromKey: (key) => `${key.replace("#", ".")}.toml`,
|
|
148
|
+
// `queryByIndex("metadataCategoriesForApp")` — no cursor, one page.
|
|
149
|
+
listingComplete: false,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: "email-template",
|
|
153
|
+
dir: "email-templates",
|
|
154
|
+
ext: ".toml",
|
|
155
|
+
stateSlot: "emailTemplates",
|
|
156
|
+
fileFromKey: tomlFile,
|
|
157
|
+
// `queryByIndex("templatesByApp")` — no cursor, one page.
|
|
158
|
+
listingComplete: false,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
/** Generic try/catch wrapper for a server fetch, in the ok/error shape. */
|
|
162
|
+
export async function attemptFetch(fn) {
|
|
163
|
+
try {
|
|
164
|
+
return { ok: true, value: await fn() };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return { ok: false, error };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* A filename must stay inside its type's directory. State is read from a file
|
|
172
|
+
* on disk, so a corrupt or hand-edited `.primitive-sync.json` must never be
|
|
173
|
+
* able to steer an `unlinkSync` outside the sync slot.
|
|
174
|
+
*/
|
|
175
|
+
export function isSafeSyncFilename(filename) {
|
|
176
|
+
if (!filename || filename === "." || filename === "..")
|
|
177
|
+
return false;
|
|
178
|
+
if (filename.includes("/") || filename.includes("\\"))
|
|
179
|
+
return false;
|
|
180
|
+
if (filename.includes("\0"))
|
|
181
|
+
return false;
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Decide which of a type's managed entity keys should have their local file
|
|
186
|
+
* removed, given the prior sync state and the outcome of this pull's fetch.
|
|
187
|
+
*
|
|
188
|
+
* A key is pruned iff it was **managed** (present in the prior
|
|
189
|
+
* `SyncState.entities[<slot>]`, so a previous pull wrote its file) and is
|
|
190
|
+
* **absent from the server keys** this pull fetched. A local file that was
|
|
191
|
+
* never in prior state was hand-authored — it is left alone and reported by
|
|
192
|
+
* `sync diff` as new.
|
|
193
|
+
*
|
|
194
|
+
* Pure and side-effect-free (the caller owns the `unlinkSync`) so the safety
|
|
195
|
+
* rules are unit-testable without a live server.
|
|
196
|
+
*/
|
|
197
|
+
export function planPrune(input) {
|
|
198
|
+
const { resourceType, priorKeys, presence, enabled } = input;
|
|
199
|
+
// Which managed keys the server did not list. Only meaningful when the fetch
|
|
200
|
+
// succeeded; on a failed fetch we know nothing and must not act.
|
|
201
|
+
const absentKeys = presence.ok
|
|
202
|
+
? priorKeys.filter((key) => !new Set(presence.serverKeys).has(key))
|
|
203
|
+
: [];
|
|
204
|
+
if (!enabled) {
|
|
205
|
+
return {
|
|
206
|
+
keys: [],
|
|
207
|
+
skipped: true,
|
|
208
|
+
skipReason: "disabled",
|
|
209
|
+
unsafeKeys: [],
|
|
210
|
+
unprunedAbsentKeys: absentKeys,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (!presence.ok) {
|
|
214
|
+
return {
|
|
215
|
+
keys: [],
|
|
216
|
+
skipped: true,
|
|
217
|
+
skipReason: "fetch-failed",
|
|
218
|
+
unsafeKeys: [],
|
|
219
|
+
unprunedAbsentKeys: [],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// A 200 is not a complete list. Deleting a file because a truncated listing
|
|
223
|
+
// omitted its entity is unrecoverable-looking to the operator (the entity is
|
|
224
|
+
// still live, so no later pull rewrites the file), which is worse than
|
|
225
|
+
// leaving a stale file behind. See `listingComplete`.
|
|
226
|
+
if (!resourceType.listingComplete) {
|
|
227
|
+
return {
|
|
228
|
+
keys: [],
|
|
229
|
+
skipped: true,
|
|
230
|
+
skipReason: "listing-incomplete",
|
|
231
|
+
unsafeKeys: [],
|
|
232
|
+
unprunedAbsentKeys: absentKeys,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const keys = [];
|
|
236
|
+
const unsafeKeys = [];
|
|
237
|
+
for (const key of absentKeys) {
|
|
238
|
+
if (!isSafeSyncFilename(resourceType.fileFromKey(key))) {
|
|
239
|
+
unsafeKeys.push(key);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
keys.push(key);
|
|
243
|
+
}
|
|
244
|
+
return { keys, skipped: false, unsafeKeys, unprunedAbsentKeys: [] };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Copy prior state entries for entities the server listed but this pull did not
|
|
248
|
+
* write (e.g. a script with no active config, or an email template whose detail
|
|
249
|
+
* fetch failed). Without this the entity would drop out of the state slot and
|
|
250
|
+
* look hand-authored to the next pull, so its stale file could never be pruned.
|
|
251
|
+
*/
|
|
252
|
+
export function preserveUnwrittenEntries(written, prior, serverKeys) {
|
|
253
|
+
if (!prior)
|
|
254
|
+
return written;
|
|
255
|
+
for (const key of serverKeys) {
|
|
256
|
+
if (!(key in written) && key in prior) {
|
|
257
|
+
written[key] = prior[key];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return written;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Best-effort check for uncommitted git changes at `path` (a file or a
|
|
264
|
+
* directory). Returns false when git isn't available, the path isn't in a
|
|
265
|
+
* repository, or anything else goes wrong — the caller treats "unknown" as
|
|
266
|
+
* "no local edits", so this never blocks on a non-git checkout.
|
|
267
|
+
*
|
|
268
|
+
* `path` is passed as an argument, never interpolated into a shell string:
|
|
269
|
+
* on the prune path its basename derives from a server entity name, and entity
|
|
270
|
+
* names are free-form (a rule set may legitimately be named ``a`b`` or `a$b`).
|
|
271
|
+
* `execFileSync` runs git directly with no shell, so no name can be read as
|
|
272
|
+
* a command.
|
|
273
|
+
*
|
|
274
|
+
* `includeUntracked` decides whether an untracked file counts as a change:
|
|
275
|
+
* - `true` (default) suits "am I about to overwrite something?" — `sync revert`
|
|
276
|
+
* would clobber an untracked file just the same.
|
|
277
|
+
* - `false` suits "did the operator edit this file?" — an untracked file is the
|
|
278
|
+
* ordinary state of a pulled file in a sync dir that was never committed, and
|
|
279
|
+
* is not evidence of a local edit.
|
|
280
|
+
*/
|
|
281
|
+
export async function hasUncommittedChanges(path, options = {}) {
|
|
282
|
+
const { includeUntracked = true } = options;
|
|
283
|
+
if (!existsSync(path))
|
|
284
|
+
return false;
|
|
285
|
+
try {
|
|
286
|
+
const { execFileSync } = await import("child_process");
|
|
287
|
+
// Run git from the path's own location, not the process cwd: the sync slot
|
|
288
|
+
// can sit in a different repository than the one the CLI was invoked from
|
|
289
|
+
// (or in none at all).
|
|
290
|
+
const cwd = statSync(path).isDirectory() ? path : dirname(path);
|
|
291
|
+
const args = ["status", "--porcelain"];
|
|
292
|
+
if (!includeUntracked)
|
|
293
|
+
args.push("-uno");
|
|
294
|
+
args.push("--", path);
|
|
295
|
+
const out = execFileSync("git", args, {
|
|
296
|
+
cwd,
|
|
297
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
298
|
+
encoding: "utf-8",
|
|
299
|
+
});
|
|
300
|
+
return out.trim().length > 0;
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The phrase pull and diff both use for an entity that is no longer exported.
|
|
308
|
+
*
|
|
309
|
+
* The plan of record asked for "archived or deleted" on the assumption that
|
|
310
|
+
* archiving drops an entity from the export. It does not: the list endpoints
|
|
311
|
+
* filter by status only when `?status=` is passed and pull never passes it
|
|
312
|
+
* (`listAppPrompts`/`listAppIntegrations` in `src/admin-api.ts`; `fetchAll` in
|
|
313
|
+
* `cli/src/commands/sync.ts`). An archived entity stays in the export and its
|
|
314
|
+
* TOML is rewritten with `status = "archived"`, so it is never pruned. The only
|
|
315
|
+
* way to be absent from the export is to have been deleted.
|
|
316
|
+
*/
|
|
317
|
+
export const ABSENT_FROM_EXPORT = "absent from sync export (deleted on server)";
|
|
318
|
+
/** `sync diff` status for a local file whose managed entity is gone. */
|
|
319
|
+
export const LOCAL_ONLY_ABSENT = "local only (absent from export)";
|
|
320
|
+
/** `sync diff` status for a local file the operator authored themselves. */
|
|
321
|
+
export const LOCAL_ONLY_NEW = "local only";
|
|
322
|
+
/**
|
|
323
|
+
* Classify a local file that has no matching server entity (issue #1659).
|
|
324
|
+
*
|
|
325
|
+
* The two cases look identical on disk but mean opposite things. A key a prior
|
|
326
|
+
* pull managed means the entity was deleted server-side and this stale file
|
|
327
|
+
* will RE-create it on the next push. A key never pulled means the operator
|
|
328
|
+
* authored the file and push creating it is the intent.
|
|
329
|
+
*/
|
|
330
|
+
export function classifyLocalOnly(priorEntities, slot, key) {
|
|
331
|
+
const managed = (priorEntities?.[slot] ?? {});
|
|
332
|
+
return key in managed ? LOCAL_ONLY_ABSENT : LOCAL_ONLY_NEW;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Remove the local files for `keys` (as decided by `planPrune`), along with any
|
|
336
|
+
* sidecar `<key>.tests/` directory.
|
|
337
|
+
*
|
|
338
|
+
* A file with uncommitted git edits is kept and reported instead: the operator
|
|
339
|
+
* may be mid-way through authoring a change they meant to push. Everything
|
|
340
|
+
* removed here is recoverable from the pre-pull snapshot via `sync revert`.
|
|
341
|
+
*/
|
|
342
|
+
export async function applyPrune(options) {
|
|
343
|
+
const { configDir, resourceType, keys, testCaseEntities, logger } = options;
|
|
344
|
+
const log = logger ?? (() => { });
|
|
345
|
+
const result = {
|
|
346
|
+
prunedKeys: [],
|
|
347
|
+
removed: [],
|
|
348
|
+
keptWithLocalEdits: [],
|
|
349
|
+
removedTestCaseKeys: [],
|
|
350
|
+
};
|
|
351
|
+
for (const key of keys) {
|
|
352
|
+
const filename = resourceType.fileFromKey(key);
|
|
353
|
+
const relativePath = `${resourceType.dir}/${filename}`;
|
|
354
|
+
const filePath = join(configDir, resourceType.dir, filename);
|
|
355
|
+
if (!existsSync(filePath))
|
|
356
|
+
continue;
|
|
357
|
+
// Untracked does not mean edited: in a sync dir that was never committed
|
|
358
|
+
// (or in no repository at all) every pulled file is untracked, and counting
|
|
359
|
+
// that as a local edit would keep every stale file and leave #1659 unfixed.
|
|
360
|
+
if (await hasUncommittedChanges(filePath, { includeUntracked: false })) {
|
|
361
|
+
result.keptWithLocalEdits.push(key);
|
|
362
|
+
log(` Kept ${relativePath} — ${ABSENT_FROM_EXPORT} but the file has ` +
|
|
363
|
+
"uncommitted local edits; remove it yourself or re-create the entity on the server");
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
unlinkSync(filePath);
|
|
367
|
+
result.prunedKeys.push(key);
|
|
368
|
+
result.removed.push(relativePath);
|
|
369
|
+
log(` Removed ${relativePath} — ${ABSENT_FROM_EXPORT}`);
|
|
370
|
+
if (!resourceType.testBlockType)
|
|
371
|
+
continue;
|
|
372
|
+
// Sidecar tests: `<key>.tests/` holds the block's test-case TOMLs and their
|
|
373
|
+
// attachment directories. With the block gone there is nothing to run them
|
|
374
|
+
// against, so they go too — along with their sync-state records.
|
|
375
|
+
const testsDirName = `${key}.tests`;
|
|
376
|
+
const testsDir = join(configDir, resourceType.dir, testsDirName);
|
|
377
|
+
if (existsSync(testsDir) && isSafeSyncFilename(testsDirName)) {
|
|
378
|
+
rmSync(testsDir, { recursive: true, force: true });
|
|
379
|
+
result.removed.push(`${resourceType.dir}/${testsDirName}`);
|
|
380
|
+
log(` Removed ${resourceType.dir}/${testsDirName} — ${ABSENT_FROM_EXPORT}`);
|
|
381
|
+
}
|
|
382
|
+
if (testCaseEntities) {
|
|
383
|
+
for (const [stateKey, entry] of Object.entries(testCaseEntities)) {
|
|
384
|
+
if (entry?.blockType === resourceType.testBlockType &&
|
|
385
|
+
entry?.blockKey === key) {
|
|
386
|
+
delete testCaseEntities[stateKey];
|
|
387
|
+
result.removedTestCaseKeys.push(stateKey);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
//# sourceMappingURL=sync-resource-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-resource-types.js","sourceRoot":"","sources":["../../../src/lib/sync-resource-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,11 +16,24 @@
|
|
|
16
16
|
* `schema-validation` descriptor) so the generated types match runtime
|
|
17
17
|
* validation exactly. A malformed schema throws (fail-and-exit).
|
|
18
18
|
* - `.runSync` is emitted only for `syncCallable: true` workflows (the server
|
|
19
|
-
* rejects run-sync otherwise); `.start
|
|
20
|
-
* non-reserved workflow (the typed async path, issue #1512 —
|
|
21
|
-
* workflows are exactly the ones that need a typed `getStatus`
|
|
22
|
-
* `.terminate`
|
|
23
|
-
*
|
|
19
|
+
* rejects run-sync otherwise); `.start`, `.getStatus`, and `.terminate` for
|
|
20
|
+
* every non-reserved workflow (the typed async path, issue #1512 —
|
|
21
|
+
* async-only workflows are exactly the ones that need a typed `getStatus` /
|
|
22
|
+
* `terminate`). `.terminate` binds `<Key>Output` (a terminated run can carry
|
|
23
|
+
* partial output); issue #1543 added the member, reversing #1512's
|
|
24
|
+
* start/status-only decision.
|
|
25
|
+
* - `.define` is emitted only for an apply-mode workflow
|
|
26
|
+
* (`requiresClientApply !== false`, the server's default). It binds
|
|
27
|
+
* `<Key>Output` so the `onApply` handler's `output` is the workflow's real
|
|
28
|
+
* output type rather than `any` (issue #1543, on top of #1542's
|
|
29
|
+
* `define<O>` / `WorkflowDefineOptions<O>` generics). `getPendingApplies`
|
|
30
|
+
* gets no member — #1542 made it a concrete `PendingApply[]` with no output
|
|
31
|
+
* to bind, and it is scoped to a document rather than a workflow.
|
|
32
|
+
* - `.cronTriggers.{create,update}` bind `<Key>Input` to the trigger's
|
|
33
|
+
* `rootInput` and pin `workflowKey`, so a cron helper can't be redirected at
|
|
34
|
+
* another workflow (issue #1543). `rootInput` is `Partial<<Key>Input>` for
|
|
35
|
+
* an object-shaped input schema (an `inputMapping` may supply the rest) and
|
|
36
|
+
* the full `<Key>Input` otherwise.
|
|
24
37
|
* - `input` is a REQUIRED option iff the schema rejects `{}` (the client's
|
|
25
38
|
* `input ?? {}` fallback would fail validation); otherwise `input?`.
|
|
26
39
|
* - The runtime call always passes the ORIGINAL key string; only identifiers
|
|
@@ -16,11 +16,24 @@
|
|
|
16
16
|
* `schema-validation` descriptor) so the generated types match runtime
|
|
17
17
|
* validation exactly. A malformed schema throws (fail-and-exit).
|
|
18
18
|
* - `.runSync` is emitted only for `syncCallable: true` workflows (the server
|
|
19
|
-
* rejects run-sync otherwise); `.start
|
|
20
|
-
* non-reserved workflow (the typed async path, issue #1512 —
|
|
21
|
-
* workflows are exactly the ones that need a typed `getStatus`
|
|
22
|
-
* `.terminate`
|
|
23
|
-
*
|
|
19
|
+
* rejects run-sync otherwise); `.start`, `.getStatus`, and `.terminate` for
|
|
20
|
+
* every non-reserved workflow (the typed async path, issue #1512 —
|
|
21
|
+
* async-only workflows are exactly the ones that need a typed `getStatus` /
|
|
22
|
+
* `terminate`). `.terminate` binds `<Key>Output` (a terminated run can carry
|
|
23
|
+
* partial output); issue #1543 added the member, reversing #1512's
|
|
24
|
+
* start/status-only decision.
|
|
25
|
+
* - `.define` is emitted only for an apply-mode workflow
|
|
26
|
+
* (`requiresClientApply !== false`, the server's default). It binds
|
|
27
|
+
* `<Key>Output` so the `onApply` handler's `output` is the workflow's real
|
|
28
|
+
* output type rather than `any` (issue #1543, on top of #1542's
|
|
29
|
+
* `define<O>` / `WorkflowDefineOptions<O>` generics). `getPendingApplies`
|
|
30
|
+
* gets no member — #1542 made it a concrete `PendingApply[]` with no output
|
|
31
|
+
* to bind, and it is scoped to a document rather than a workflow.
|
|
32
|
+
* - `.cronTriggers.{create,update}` bind `<Key>Input` to the trigger's
|
|
33
|
+
* `rootInput` and pin `workflowKey`, so a cron helper can't be redirected at
|
|
34
|
+
* another workflow (issue #1543). `rootInput` is `Partial<<Key>Input>` for
|
|
35
|
+
* an object-shaped input schema (an `inputMapping` may supply the rest) and
|
|
36
|
+
* the full `<Key>Input` otherwise.
|
|
24
37
|
* - `input` is a REQUIRED option iff the schema rejects `{}` (the client's
|
|
25
38
|
* `input ?? {}` fallback would fail validation); otherwise `input?`.
|
|
26
39
|
* - The runtime call always passes the ORIGINAL key string; only identifiers
|
|
@@ -30,26 +43,16 @@
|
|
|
30
43
|
* shared `writeOrCheckGeneratedFiles`, exactly like `databases codegen`.
|
|
31
44
|
*/
|
|
32
45
|
import * as path from "path";
|
|
33
|
-
import { parseConfigToml } from "../config-toml.js";
|
|
34
|
-
import { safeJsonParse } from "../workflow-payload.js";
|
|
35
46
|
import { writeOrCheckGeneratedFiles, } from "../codegen-shared/generatedFiles.js";
|
|
36
47
|
import { fingerprintToml } from "../db-codegen/dbFingerprint.js";
|
|
37
|
-
import { schemaToTsType
|
|
48
|
+
import { schemaToTsType } from "./schemaToTs.js";
|
|
38
49
|
import { describeSchema } from "./generated-schema-descriptor.js";
|
|
50
|
+
import { describeWorkflowInvoker } from "./invokerIR.js";
|
|
39
51
|
import { workflowInputTypeName, workflowOutputTypeName, workflowFactoryName, } from "./naming.js";
|
|
40
|
-
/** Prefix marking a platform-owned workflow key (mirrors the server's
|
|
41
|
-
* `INTERNAL_WORKFLOW_KEY_PREFIX`; inlined because the CLI cannot import from
|
|
42
|
-
* `src/`). Such workflows are never client-invocable and are skipped. */
|
|
43
|
-
const INTERNAL_WORKFLOW_KEY_PREFIX = "__internal.";
|
|
44
52
|
/** Suffix identifying a generated file (shared with `databases codegen`). */
|
|
45
53
|
export const GENERATED_FILE_SUFFIX = ".generated.ts";
|
|
46
54
|
/** The client package the generated file imports its types from. */
|
|
47
55
|
const CLIENT_PACKAGE = "js-bao-wss-client";
|
|
48
|
-
/** True for a platform-owned workflow key. */
|
|
49
|
-
function isReservedKey(key) {
|
|
50
|
-
return (typeof key === "string" &&
|
|
51
|
-
key.trim().toLowerCase().startsWith(INTERNAL_WORKFLOW_KEY_PREFIX));
|
|
52
|
-
}
|
|
53
56
|
/**
|
|
54
57
|
* Render a top-level `<Name>Input`/`<Name>Output` declaration. An absent schema
|
|
55
58
|
* → `unknown`. A plain object type → an `interface`; anything else → a `type`
|
|
@@ -74,35 +77,39 @@ function renderTypeDecl(name, schema) {
|
|
|
74
77
|
* Parse a single workflow TOML into the data the emitter needs. Throws on
|
|
75
78
|
* malformed schema JSON (fail-and-exit) — the honest `unknown` fallback is for
|
|
76
79
|
* valid-but-untypeable schemas only. Returns null for a reserved workflow.
|
|
80
|
+
*
|
|
81
|
+
* The operation metadata (key, `syncCallable`, required-vs-optional `input`,
|
|
82
|
+
* parsed schemas) comes from the SHARED `describeWorkflowInvoker` IR so the TS
|
|
83
|
+
* and Swift invokers can never disagree (#1547 binding item 5). Only the
|
|
84
|
+
* TS-specific naming + type-declaration rendering happens here.
|
|
77
85
|
*/
|
|
78
86
|
function parseWorkflow(input) {
|
|
79
87
|
// #1464 removed `@iarna/toml`; #1446 makes `sync pull` emit schemas as native
|
|
80
88
|
// TOML tables (incl. mixed-type enums like `enum = ["ok", 1]`, TOML 1.0). The
|
|
81
|
-
// `parseConfigToml`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// safeJsonParse throws on invalid JSON in a schema string — let it bubble.
|
|
91
|
-
const inputSchema = safeJsonParse(workflow.inputSchema);
|
|
92
|
-
const outputSchema = safeJsonParse(workflow.outputSchema);
|
|
93
|
-
const inputTypeName = workflowInputTypeName(key);
|
|
94
|
-
const outputTypeName = workflowOutputTypeName(key);
|
|
89
|
+
// shared IR uses `parseConfigToml` + `safeJsonParse` under the hood.
|
|
90
|
+
const ir = describeWorkflowInvoker({
|
|
91
|
+
fileStem: input.fileStem,
|
|
92
|
+
tomlContent: input.tomlContent,
|
|
93
|
+
});
|
|
94
|
+
if (!ir)
|
|
95
|
+
return null; // reserved workflow
|
|
96
|
+
const inputTypeName = workflowInputTypeName(ir.key);
|
|
97
|
+
const outputTypeName = workflowOutputTypeName(ir.key);
|
|
95
98
|
return {
|
|
96
|
-
key,
|
|
97
|
-
syncCallable:
|
|
99
|
+
key: ir.key,
|
|
100
|
+
syncCallable: ir.syncCallable,
|
|
98
101
|
inputTypeName,
|
|
99
102
|
outputTypeName,
|
|
100
|
-
factoryName: workflowFactoryName(key),
|
|
101
|
-
inputDecl: renderTypeDecl(inputTypeName, inputSchema),
|
|
102
|
-
outputDecl: renderTypeDecl(outputTypeName, outputSchema),
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
|
|
103
|
+
factoryName: workflowFactoryName(ir.key),
|
|
104
|
+
inputDecl: renderTypeDecl(inputTypeName, ir.inputSchema),
|
|
105
|
+
outputDecl: renderTypeDecl(outputTypeName, ir.outputSchema),
|
|
106
|
+
inputRequired: ir.inputRequired,
|
|
107
|
+
// Object-shaped only when the schema declares exactly `type: "object"` —
|
|
108
|
+
// the one case where `Partial<<Key>Input>` is a sound cron `rootInput`
|
|
109
|
+
// type. Schema-less / union / scalar / array → not object-shaped.
|
|
110
|
+
inputIsObjectShaped: ir.inputSchema !== undefined &&
|
|
111
|
+
describeSchema(ir.inputSchema).singularType === "object",
|
|
112
|
+
requiresClientApply: ir.requiresClientApply,
|
|
106
113
|
};
|
|
107
114
|
}
|
|
108
115
|
/** Render the typed invoker factory for one workflow. */
|
|
@@ -149,6 +156,65 @@ function renderFactory(wf) {
|
|
|
149
156
|
` ...opts,\n` +
|
|
150
157
|
` workflowKey: ${keyLiteral},\n` +
|
|
151
158
|
` }),`);
|
|
159
|
+
// Typed termination (#1543), emitted for EVERY non-reserved workflow — any
|
|
160
|
+
// run can be terminated. `output` is bound to `<Key>Output` (a terminated run
|
|
161
|
+
// can carry partial output), same schema-less → `unknown` rule as getStatus.
|
|
162
|
+
// `runKey` stays required. Same spread-first / key-last ordering so an untyped
|
|
163
|
+
// caller cannot override the key and terminate the wrong workflow's run. This
|
|
164
|
+
// member reverses #1512's start/status-only decision.
|
|
165
|
+
lines.push(` terminate: (opts: Omit<TerminateWorkflowOptions, "workflowKey">) =>\n` +
|
|
166
|
+
` client.workflows.terminate<${wf.outputTypeName}>({\n` +
|
|
167
|
+
` ...opts,\n` +
|
|
168
|
+
` workflowKey: ${keyLiteral},\n` +
|
|
169
|
+
` }),`);
|
|
170
|
+
// Typed apply handler (#1543 Phase 2), emitted ONLY for an apply-mode
|
|
171
|
+
// workflow (`requiresClientApply !== false` — the same default the server
|
|
172
|
+
// uses to decide a completed run needs a client apply). `WorkflowDefineOptions<O>`
|
|
173
|
+
// and `WorkflowApplyHandler<O>` come from #1542; binding `<Key>Output` here
|
|
174
|
+
// makes the `onApply` callback's `output` the workflow's real output type
|
|
175
|
+
// instead of `any`. The key is passed as a positional argument, so there is
|
|
176
|
+
// no spread a caller could use to override it.
|
|
177
|
+
//
|
|
178
|
+
// No `getPendingApplies` member: #1542 shipped it as a concrete
|
|
179
|
+
// `PendingApply[]` (the server element carries no `output`, so there is no
|
|
180
|
+
// output generic to bind), and the call is scoped to a DOCUMENT rather than
|
|
181
|
+
// a workflow — a per-workflow member would bind nothing and would wrongly
|
|
182
|
+
// suggest the result only covers this workflow.
|
|
183
|
+
if (wf.requiresClientApply) {
|
|
184
|
+
lines.push(` define: (options: WorkflowDefineOptions<${wf.outputTypeName}>) =>\n` +
|
|
185
|
+
` client.workflows.define<${wf.outputTypeName}>(${keyLiteral}, options),`);
|
|
186
|
+
}
|
|
187
|
+
// Typed cron-trigger create/update (#1543), folded INTO the factory (not a
|
|
188
|
+
// separate export). The client's own `rootInput` generic (#1542) is bound to
|
|
189
|
+
// `<Key>Input`: `Partial<...>` for an object-shaped schema (an `inputMapping`
|
|
190
|
+
// may supply the rest), the full type otherwise. Binding the generic rather
|
|
191
|
+
// than restating the field keeps the caller-facing params in step with the
|
|
192
|
+
// client contract — notably `update` accepts `rootInput: null` to clear a
|
|
193
|
+
// stored root input, while `create` does not (the server rejects a null there
|
|
194
|
+
// with a 400). `workflowKey` is Omit-ted from the caller-facing params and
|
|
195
|
+
// pinned LAST so a caller can't redirect the trigger at another workflow.
|
|
196
|
+
// Nothing is spread after `workflowKey` — `rootInput` rides in via `...params`
|
|
197
|
+
// — preserving the key-last convention.
|
|
198
|
+
const rootInputType = wf.inputIsObjectShaped
|
|
199
|
+
? `Partial<${wf.inputTypeName}>`
|
|
200
|
+
: wf.inputTypeName;
|
|
201
|
+
lines.push(` cronTriggers: {\n` +
|
|
202
|
+
` create: (\n` +
|
|
203
|
+
` params: Omit<CreateCronTriggerParams<${rootInputType}>, "workflowKey">\n` +
|
|
204
|
+
` ) =>\n` +
|
|
205
|
+
` client.cronTriggers.create<${rootInputType}>({\n` +
|
|
206
|
+
` ...params,\n` +
|
|
207
|
+
` workflowKey: ${keyLiteral},\n` +
|
|
208
|
+
` }),\n` +
|
|
209
|
+
` update: (\n` +
|
|
210
|
+
` triggerId: string,\n` +
|
|
211
|
+
` params: Omit<UpdateCronTriggerParams<${rootInputType}>, "workflowKey">\n` +
|
|
212
|
+
` ) =>\n` +
|
|
213
|
+
` client.cronTriggers.update<${rootInputType}>(triggerId, {\n` +
|
|
214
|
+
` ...params,\n` +
|
|
215
|
+
` workflowKey: ${keyLiteral},\n` +
|
|
216
|
+
` }),\n` +
|
|
217
|
+
` },`);
|
|
152
218
|
lines.push(` };`, `}`);
|
|
153
219
|
return lines.join("\n");
|
|
154
220
|
}
|
|
@@ -163,15 +229,24 @@ export function renderWorkflowFile(input) {
|
|
|
163
229
|
return null;
|
|
164
230
|
const fingerprint = fingerprintToml(input.tomlContent);
|
|
165
231
|
// Only import the option types actually referenced, so the generated file
|
|
166
|
-
// compiles under a consumer's `noUnusedLocals`. `GetWorkflowStatusOptions
|
|
167
|
-
//
|
|
232
|
+
// compiles under a consumer's `noUnusedLocals`. `GetWorkflowStatusOptions`,
|
|
233
|
+
// `TerminateWorkflowOptions`, and the two cron-param types are always
|
|
234
|
+
// referenced now — every factory emits `getStatus`, `terminate` (#1512 /
|
|
235
|
+
// #1543), and `cronTriggers.{create,update}` (#1543).
|
|
168
236
|
const imports = [
|
|
237
|
+
"CreateCronTriggerParams",
|
|
169
238
|
"GetWorkflowStatusOptions",
|
|
170
239
|
"JsBaoClient",
|
|
171
240
|
"StartWorkflowOptions",
|
|
241
|
+
"TerminateWorkflowOptions",
|
|
242
|
+
"UpdateCronTriggerParams",
|
|
172
243
|
];
|
|
173
244
|
if (wf.syncCallable)
|
|
174
245
|
imports.push("RunSyncWorkflowOptions");
|
|
246
|
+
// Only an apply-mode workflow emits `define`, so its option type is imported
|
|
247
|
+
// only then — a non-apply-mode file would otherwise fail `noUnusedLocals`.
|
|
248
|
+
if (wf.requiresClientApply)
|
|
249
|
+
imports.push("WorkflowDefineOptions");
|
|
175
250
|
imports.sort();
|
|
176
251
|
const blocks = [];
|
|
177
252
|
blocks.push([
|