primitive-admin 1.0.21 → 1.0.23
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 +28 -6
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/auth.js +1 -1
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/database-types.js +453 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.js +645 -0
- package/dist/src/commands/databases.js.map +1 -0
- package/dist/src/commands/email-templates.js +267 -0
- package/dist/src/commands/email-templates.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 +366 -0
- package/dist/src/commands/groups.js.map +1 -0
- package/dist/src/commands/init.js +125 -48
- package/dist/src/commands/init.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/sync.js +117 -3
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/lib/api-client.js +18 -0
- package/dist/src/lib/api-client.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/template.js +35 -14
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/version-check.js +169 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { ApiClient } from "../lib/api-client.js";
|
|
2
|
+
import { getCurrentAppId } from "../lib/config.js";
|
|
3
|
+
import { success, error, info, keyValue, formatTable, formatId, formatDate, json, } from "../lib/output.js";
|
|
4
|
+
function resolveAppId(appId, options) {
|
|
5
|
+
const resolved = appId || options.app || getCurrentAppId();
|
|
6
|
+
if (!resolved) {
|
|
7
|
+
error("No app specified. Use <app-id>, --app, or 'primitive use <app-id>' to set context.");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
return resolved;
|
|
11
|
+
}
|
|
12
|
+
export function registerRuleSetsCommands(program) {
|
|
13
|
+
const ruleSets = program
|
|
14
|
+
.command("rule-sets")
|
|
15
|
+
.description("Create and manage access rule sets for databases")
|
|
16
|
+
.addHelpText("after", `
|
|
17
|
+
Examples:
|
|
18
|
+
$ primitive rule-sets list
|
|
19
|
+
$ primitive rule-sets create "My Rules" --resource-type database --rules '{"Task":{"read":"true","write":"record.createdBy == user.userId"}}'
|
|
20
|
+
$ primitive rule-sets get <rule-set-id>
|
|
21
|
+
$ primitive rule-sets update <rule-set-id> --name "Updated Rules"
|
|
22
|
+
$ primitive rule-sets delete <rule-set-id>
|
|
23
|
+
`);
|
|
24
|
+
// List rule sets
|
|
25
|
+
ruleSets
|
|
26
|
+
.command("list")
|
|
27
|
+
.description("List access rule sets in an app")
|
|
28
|
+
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
29
|
+
.option("--app <app-id>", "App ID")
|
|
30
|
+
.option("--resource-type <type>", "Filter by resource type (database, document, workflow, api)")
|
|
31
|
+
.option("--json", "Output as JSON")
|
|
32
|
+
.action(async (appId, options) => {
|
|
33
|
+
const resolvedAppId = resolveAppId(appId, options);
|
|
34
|
+
const client = new ApiClient();
|
|
35
|
+
try {
|
|
36
|
+
const params = {};
|
|
37
|
+
if (options.resourceType)
|
|
38
|
+
params.resourceType = options.resourceType;
|
|
39
|
+
const result = await client.listRuleSets(resolvedAppId, params);
|
|
40
|
+
const list = Array.isArray(result) ? result : result?.ruleSets ?? [];
|
|
41
|
+
if (options.json) {
|
|
42
|
+
json(list);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (list.length === 0) {
|
|
46
|
+
info("No rule sets found.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log(formatTable(list, [
|
|
50
|
+
{ header: "ID", key: "ruleSetId", format: formatId },
|
|
51
|
+
{ header: "NAME", key: "name" },
|
|
52
|
+
{ header: "TYPE", key: "resourceType" },
|
|
53
|
+
{ header: "VERSION", key: "version" },
|
|
54
|
+
{ header: "MODIFIED", key: "modifiedAt", format: formatDate },
|
|
55
|
+
]));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
error(err.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// Create rule set
|
|
63
|
+
ruleSets
|
|
64
|
+
.command("create")
|
|
65
|
+
.description("Create a new access rule set")
|
|
66
|
+
.argument("<name>", "Rule set name")
|
|
67
|
+
.requiredOption("--resource-type <type>", "Resource type: database, document, workflow, or api")
|
|
68
|
+
.requiredOption("--rules <json>", "Rules as JSON string")
|
|
69
|
+
.option("--description <text>", "Optional description")
|
|
70
|
+
.option("--app <app-id>", "App ID")
|
|
71
|
+
.option("--json", "Output as JSON")
|
|
72
|
+
.action(async (name, options) => {
|
|
73
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
74
|
+
const client = new ApiClient();
|
|
75
|
+
let rules;
|
|
76
|
+
try {
|
|
77
|
+
rules = JSON.parse(options.rules);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
error("Invalid JSON in --rules. Provide a valid JSON object.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const data = {
|
|
85
|
+
name,
|
|
86
|
+
resourceType: options.resourceType,
|
|
87
|
+
rules,
|
|
88
|
+
};
|
|
89
|
+
if (options.description)
|
|
90
|
+
data.description = options.description;
|
|
91
|
+
const result = await client.createRuleSet(resolvedAppId, data);
|
|
92
|
+
if (options.json) {
|
|
93
|
+
json(result);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
success("Rule set created.");
|
|
97
|
+
keyValue("Rule Set ID", result.ruleSetId);
|
|
98
|
+
keyValue("Name", result.name);
|
|
99
|
+
keyValue("Resource Type", result.resourceType);
|
|
100
|
+
keyValue("Version", result.version);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
error(err.message);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Get rule set
|
|
108
|
+
ruleSets
|
|
109
|
+
.command("get")
|
|
110
|
+
.description("Get rule set details")
|
|
111
|
+
.argument("<rule-set-id>", "Rule set ID")
|
|
112
|
+
.option("--app <app-id>", "App ID")
|
|
113
|
+
.option("--json", "Output as JSON")
|
|
114
|
+
.action(async (ruleSetId, options) => {
|
|
115
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
116
|
+
const client = new ApiClient();
|
|
117
|
+
try {
|
|
118
|
+
const result = await client.getRuleSet(resolvedAppId, ruleSetId);
|
|
119
|
+
if (options.json) {
|
|
120
|
+
json(result);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
keyValue("Rule Set ID", result.ruleSetId);
|
|
124
|
+
keyValue("Name", result.name);
|
|
125
|
+
if (result.description)
|
|
126
|
+
keyValue("Description", result.description);
|
|
127
|
+
keyValue("Resource Type", result.resourceType);
|
|
128
|
+
keyValue("Version", String(result.version));
|
|
129
|
+
keyValue("Created", formatDate(result.createdAt));
|
|
130
|
+
keyValue("Modified", formatDate(result.modifiedAt));
|
|
131
|
+
console.log("\nRules:");
|
|
132
|
+
console.log(JSON.stringify(result.rules, null, 2));
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
error(err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// Update rule set
|
|
140
|
+
ruleSets
|
|
141
|
+
.command("update")
|
|
142
|
+
.description("Update a rule set")
|
|
143
|
+
.argument("<rule-set-id>", "Rule set ID")
|
|
144
|
+
.option("--name <name>", "New name")
|
|
145
|
+
.option("--description <text>", "New description")
|
|
146
|
+
.option("--rules <json>", "New rules as JSON string")
|
|
147
|
+
.option("--app <app-id>", "App ID")
|
|
148
|
+
.option("--json", "Output as JSON")
|
|
149
|
+
.action(async (ruleSetId, options) => {
|
|
150
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
151
|
+
const client = new ApiClient();
|
|
152
|
+
const data = {};
|
|
153
|
+
if (options.name)
|
|
154
|
+
data.name = options.name;
|
|
155
|
+
if (options.description !== undefined)
|
|
156
|
+
data.description = options.description;
|
|
157
|
+
if (options.rules) {
|
|
158
|
+
try {
|
|
159
|
+
data.rules = JSON.parse(options.rules);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
error("Invalid JSON in --rules. Provide a valid JSON object.");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (Object.keys(data).length === 0) {
|
|
167
|
+
error("Nothing to update. Provide --name, --description, or --rules.");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const result = await client.updateRuleSet(resolvedAppId, ruleSetId, data);
|
|
172
|
+
if (options.json) {
|
|
173
|
+
json(result);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
success("Rule set updated.");
|
|
177
|
+
keyValue("Rule Set ID", result.ruleSetId);
|
|
178
|
+
keyValue("Name", result.name);
|
|
179
|
+
keyValue("Version", String(result.version));
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
error(err.message);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Delete rule set
|
|
187
|
+
ruleSets
|
|
188
|
+
.command("delete")
|
|
189
|
+
.description("Delete a rule set")
|
|
190
|
+
.argument("<rule-set-id>", "Rule set ID")
|
|
191
|
+
.option("--app <app-id>", "App ID")
|
|
192
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
193
|
+
.action(async (ruleSetId, options) => {
|
|
194
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
195
|
+
if (!options.yes) {
|
|
196
|
+
const inquirer = await import("inquirer");
|
|
197
|
+
const { confirm } = await inquirer.default.prompt([
|
|
198
|
+
{
|
|
199
|
+
type: "confirm",
|
|
200
|
+
name: "confirm",
|
|
201
|
+
message: `Delete rule set ${ruleSetId}? This cannot be undone.`,
|
|
202
|
+
default: false,
|
|
203
|
+
},
|
|
204
|
+
]);
|
|
205
|
+
if (!confirm) {
|
|
206
|
+
info("Cancelled.");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const client = new ApiClient();
|
|
211
|
+
try {
|
|
212
|
+
await client.deleteRuleSet(resolvedAppId, ruleSetId);
|
|
213
|
+
success(`Rule set ${ruleSetId} deleted.`);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
error(err.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// Schema
|
|
221
|
+
ruleSets
|
|
222
|
+
.command("schema")
|
|
223
|
+
.description("Display the rule set schema (available context variables and functions)")
|
|
224
|
+
.option("--app <app-id>", "App ID")
|
|
225
|
+
.option("--json", "Output as JSON")
|
|
226
|
+
.action(async (options) => {
|
|
227
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
228
|
+
const client = new ApiClient();
|
|
229
|
+
try {
|
|
230
|
+
const result = await client.getRuleSetSchema(resolvedAppId);
|
|
231
|
+
if (options.json) {
|
|
232
|
+
json(result);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
console.log(JSON.stringify(result, null, 2));
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
error(err.message);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
// Test / dry-run
|
|
243
|
+
ruleSets
|
|
244
|
+
.command("test")
|
|
245
|
+
.description("Test a rule set against a simulated scenario (dry-run)")
|
|
246
|
+
.argument("<rule-set-id>", "Rule set ID")
|
|
247
|
+
.requiredOption("--scenario <json>", "Scenario as JSON string")
|
|
248
|
+
.option("--app <app-id>", "App ID")
|
|
249
|
+
.option("--json", "Output as JSON")
|
|
250
|
+
.action(async (ruleSetId, options) => {
|
|
251
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
252
|
+
const client = new ApiClient();
|
|
253
|
+
let scenario;
|
|
254
|
+
try {
|
|
255
|
+
scenario = JSON.parse(options.scenario);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
error("Invalid JSON in --scenario. Provide a valid JSON object.");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const result = await client.testRuleSet(resolvedAppId, ruleSetId, scenario);
|
|
263
|
+
if (options.json) {
|
|
264
|
+
json(result);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (result.error) {
|
|
268
|
+
info(`Result: DENIED`);
|
|
269
|
+
keyValue("Error", result.error);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
info(`Result: ${result.allowed ? "ALLOWED" : "DENIED"}`);
|
|
273
|
+
if (result.expression)
|
|
274
|
+
keyValue("Expression", result.expression);
|
|
275
|
+
}
|
|
276
|
+
if (result.context) {
|
|
277
|
+
console.log("\nContext:");
|
|
278
|
+
console.log(JSON.stringify(result.context, null, 2));
|
|
279
|
+
}
|
|
280
|
+
if (result.trace && result.trace.length > 0) {
|
|
281
|
+
console.log("\nTrace:");
|
|
282
|
+
console.log(formatTable(result.trace, [
|
|
283
|
+
{ header: "FUNCTION", key: "function" },
|
|
284
|
+
{ header: "ARGS", key: "args", format: (v) => JSON.stringify(v) },
|
|
285
|
+
{ header: "RESULT", key: "result", format: (v) => JSON.stringify(v) },
|
|
286
|
+
]));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
error(err.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
// Debug (live evaluation with real data)
|
|
295
|
+
ruleSets
|
|
296
|
+
.command("debug")
|
|
297
|
+
.description("Debug a rule evaluation using real user and group data (console-admin only)")
|
|
298
|
+
.requiredOption("--user <userId>", "Target user ID")
|
|
299
|
+
.requiredOption("--group-type <type>", "Group type")
|
|
300
|
+
.requiredOption("--category <category>", "Rule category (group or member)")
|
|
301
|
+
.requiredOption("--operation <operation>", "Rule operation (create, edit, delete, list)")
|
|
302
|
+
.option("--group-id <groupId>", "Group ID (for operations on existing groups)")
|
|
303
|
+
.option("--target-user <userId>", "Target user ID (for member operations)")
|
|
304
|
+
.option("--app <app-id>", "App ID")
|
|
305
|
+
.option("--json", "Output as JSON")
|
|
306
|
+
.action(async (options) => {
|
|
307
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
308
|
+
const client = new ApiClient();
|
|
309
|
+
const data = {
|
|
310
|
+
userId: options.user,
|
|
311
|
+
groupType: options.groupType,
|
|
312
|
+
category: options.category,
|
|
313
|
+
operation: options.operation,
|
|
314
|
+
};
|
|
315
|
+
if (options.groupId)
|
|
316
|
+
data.groupId = options.groupId;
|
|
317
|
+
if (options.targetUser)
|
|
318
|
+
data.targetUserId = options.targetUser;
|
|
319
|
+
try {
|
|
320
|
+
const result = await client.debugRuleSet(resolvedAppId, data);
|
|
321
|
+
if (options.json) {
|
|
322
|
+
json(result);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
info(`Result: ${result.allowed ? "ALLOWED" : "DENIED"}`);
|
|
326
|
+
if (result.reason)
|
|
327
|
+
keyValue("Reason", result.reason);
|
|
328
|
+
if (result.expression)
|
|
329
|
+
keyValue("Expression", result.expression);
|
|
330
|
+
if (result.ruleSetId)
|
|
331
|
+
keyValue("Rule Set", `${result.ruleSetName} (${result.ruleSetId})`);
|
|
332
|
+
if (result.user) {
|
|
333
|
+
console.log("\nUser:");
|
|
334
|
+
keyValue(" User ID", result.user.userId);
|
|
335
|
+
keyValue(" App Role", result.user.appRole);
|
|
336
|
+
}
|
|
337
|
+
if (result.memberships && result.memberships.length > 0) {
|
|
338
|
+
console.log("\nMemberships:");
|
|
339
|
+
console.log(formatTable(result.memberships, [
|
|
340
|
+
{ header: "GROUP TYPE", key: "groupType" },
|
|
341
|
+
{ header: "GROUP ID", key: "groupId", format: formatId },
|
|
342
|
+
]));
|
|
343
|
+
}
|
|
344
|
+
else if (result.memberships) {
|
|
345
|
+
console.log("\nMemberships: (none)");
|
|
346
|
+
}
|
|
347
|
+
if (result.context) {
|
|
348
|
+
console.log("\nContext:");
|
|
349
|
+
console.log(JSON.stringify(result.context, null, 2));
|
|
350
|
+
}
|
|
351
|
+
if (result.trace && result.trace.length > 0) {
|
|
352
|
+
console.log("\nTrace:");
|
|
353
|
+
console.log(formatTable(result.trace, [
|
|
354
|
+
{ header: "FUNCTION", key: "function" },
|
|
355
|
+
{ header: "ARGS", key: "args", format: (v) => JSON.stringify(v) },
|
|
356
|
+
{ header: "RESULT", key: "result", format: (v) => JSON.stringify(v) },
|
|
357
|
+
]));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
error(err.message);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=rule-sets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-sets.js","sourceRoot":"","sources":["../../../src/commands/rule-sets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EACL,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,UAAU,EACV,IAAI,GACL,MAAM,kBAAkB,CAAC;AAE1B,SAAS,YAAY,CAAC,KAAyB,EAAE,OAAY;IAC3D,MAAM,QAAQ,GAAG,KAAK,IAAI,OAAO,CAAC,GAAG,IAAI,eAAe,EAAE,CAAC;IAC3D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,KAAK,CAAC,oFAAoF,CAAC,CAAC;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAgB;IACvD,MAAM,QAAQ,GAAG,OAAO;SACrB,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,kDAAkD,CAAC;SAC/D,WAAW,CAAC,OAAO,EAAE;;;;;;;CAOzB,CAAC,CAAC;IAED,iBAAiB;IACjB,QAAQ;SACL,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,iCAAiC,CAAC;SAC9C,QAAQ,CAAC,UAAU,EAAE,4CAA4C,CAAC;SAClE,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,wBAAwB,EAAE,6DAA6D,CAAC;SAC/F,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC/B,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAQ,EAAE,CAAC;YACvB,IAAI,OAAO,CAAC,YAAY;gBAAE,MAAM,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;YAErE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAChE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,MAAc,EAAE,QAAQ,IAAI,EAAE,CAAC;YAE9E,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,CAAC;gBACX,OAAO;YACT,CAAC;YAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,qBAAqB,CAAC,CAAC;gBAC5B,OAAO;YACT,CAAC;YAED,OAAO,CAAC,GAAG,CACT,WAAW,CAAC,IAAI,EAAE;gBAChB,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE;gBACpD,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE;gBAC/B,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,EAAE;gBACvC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE;gBACrC,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE;aAC9D,CAAC,CACH,CAAC;QACJ,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,kBAAkB;IAClB,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,8BAA8B,CAAC;SAC3C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;SACnC,cAAc,CAAC,wBAAwB,EAAE,qDAAqD,CAAC;SAC/F,cAAc,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;SACxD,MAAM,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;SACtD,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QAC9B,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,IAAI,KAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAQ;gBAChB,IAAI;gBACJ,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,KAAK;aACN,CAAC;YACF,IAAI,OAAO,CAAC,WAAW;gBAAE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;YAEhE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YAE/D,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YAED,OAAO,CAAC,mBAAmB,CAAC,CAAC;YAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;YAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,eAAe;IACf,QAAQ;SACL,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,sBAAsB,CAAC;SACnC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;SACxC,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QACnC,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;YAEjE,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,MAAM,CAAC,WAAW;gBAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YACpE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;YAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5C,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;YAClD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACpD,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,kBAAkB;IAClB,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,mBAAmB,CAAC;SAChC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;SACxC,MAAM,CAAC,eAAe,EAAE,UAAU,CAAC;SACnC,MAAM,CAAC,sBAAsB,EAAE,iBAAiB,CAAC;SACjD,MAAM,CAAC,gBAAgB,EAAE,0BAA0B,CAAC;SACpD,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QACnC,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,MAAM,IAAI,GAAQ,EAAE,CAAC;QACrB,IAAI,OAAO,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC3C,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QAC9E,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,KAAK,CAAC,uDAAuD,CAAC,CAAC;gBAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,KAAK,CAAC,+DAA+D,CAAC,CAAC;YACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,aAAa,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YAE1E,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YAED,OAAO,CAAC,mBAAmB,CAAC,CAAC;YAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,kBAAkB;IAClB,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,mBAAmB,CAAC;SAChC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;SACxC,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;SAC/C,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QACnC,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAEvD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YACjB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YAC1C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;gBAChD;oBACE,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,mBAAmB,SAAS,0BAA0B;oBAC/D,OAAO,EAAE,KAAK;iBACf;aACF,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,YAAY,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,aAAa,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;YACrD,OAAO,CAAC,YAAY,SAAS,WAAW,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,SAAS;IACT,QAAQ;SACL,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,yEAAyE,CAAC;SACtF,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;YAE5D,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,iBAAiB;IACjB,QAAQ;SACL,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,wDAAwD,CAAC;SACrE,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;SACxC,cAAc,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;SAC9D,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QACnC,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,IAAI,QAAa,CAAC;QAClB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,aAAa,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;YAE5E,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YAED,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,IAAI,CAAC,gBAAgB,CAAC,CAAC;gBACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,WAAW,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACzD,IAAI,MAAM,CAAC,UAAU;oBAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBAC1B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACvD,CAAC;YACD,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACxB,OAAO,CAAC,GAAG,CACT,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE;oBACxB,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE;oBACvC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;oBACtE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;iBAC3E,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,yCAAyC;IACzC,QAAQ;SACL,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,6EAA6E,CAAC;SAC1F,cAAc,CAAC,iBAAiB,EAAE,gBAAgB,CAAC;SACnD,cAAc,CAAC,qBAAqB,EAAE,YAAY,CAAC;SACnD,cAAc,CAAC,uBAAuB,EAAE,iCAAiC,CAAC;SAC1E,cAAc,CAAC,yBAAyB,EAAE,6CAA6C,CAAC;SACxF,MAAM,CAAC,sBAAsB,EAAE,8CAA8C,CAAC;SAC9E,MAAM,CAAC,wBAAwB,EAAE,wCAAwC,CAAC;SAC1E,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC;SAClC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAE/B,MAAM,IAAI,GAAQ;YAChB,MAAM,EAAE,OAAO,CAAC,IAAI;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC;QACF,IAAI,OAAO,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QACpD,IAAI,OAAO,CAAC,UAAU;YAAE,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;QAE/D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YAE9D,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YAED,IAAI,CAAC,WAAW,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACzD,IAAI,MAAM,CAAC,MAAM;gBAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACrD,IAAI,MAAM,CAAC,UAAU;gBAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YACjE,IAAI,MAAM,CAAC,SAAS;gBAAE,QAAQ,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;YAE1F,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9C,CAAC;YAED,IAAI,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAC9B,OAAO,CAAC,GAAG,CACT,WAAW,CAAC,MAAM,CAAC,WAAW,EAAE;oBAC9B,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE;oBAC1C,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;iBACzD,CAAC,CACH,CAAC;YACJ,CAAC;iBAAM,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBAC1B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACvD,CAAC;YAED,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACxB,OAAO,CAAC,GAAG,CACT,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE;oBACxB,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE;oBACvC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;oBACtE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;iBAC3E,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AAEP,CAAC"}
|
|
@@ -127,6 +127,24 @@ function serializeWorkflow(workflow, draft, configs) {
|
|
|
127
127
|
};
|
|
128
128
|
return TOML.stringify(data);
|
|
129
129
|
}
|
|
130
|
+
function serializeEmailTemplate(template) {
|
|
131
|
+
// The API returns { emailType, hasOverride, override: { subject, htmlBody, textBody }, default: { ... } }
|
|
132
|
+
// We only serialize overrides (custom templates), using the override fields.
|
|
133
|
+
const override = template.override || {};
|
|
134
|
+
const data = {
|
|
135
|
+
template: {
|
|
136
|
+
emailType: template.emailType,
|
|
137
|
+
subject: override.subject || "",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
if (override.htmlBody) {
|
|
141
|
+
data.template.htmlBody = override.htmlBody;
|
|
142
|
+
}
|
|
143
|
+
if (override.textBody) {
|
|
144
|
+
data.template.textBody = override.textBody;
|
|
145
|
+
}
|
|
146
|
+
return TOML.stringify(data);
|
|
147
|
+
}
|
|
130
148
|
// Parsing helpers
|
|
131
149
|
function parseTomlFile(filePath) {
|
|
132
150
|
const content = readFileSync(filePath, "utf-8");
|
|
@@ -466,6 +484,7 @@ Directory Structure:
|
|
|
466
484
|
prompts/{key}.tests/*.toml # Prompt test cases
|
|
467
485
|
workflows/*.toml # Workflow definitions
|
|
468
486
|
workflows/{key}.tests/*.toml # Workflow test cases
|
|
487
|
+
email-templates/*.toml # Email template overrides
|
|
469
488
|
`);
|
|
470
489
|
// Init
|
|
471
490
|
sync
|
|
@@ -481,6 +500,7 @@ Directory Structure:
|
|
|
481
500
|
ensureDir(join(configDir, "integrations"));
|
|
482
501
|
ensureDir(join(configDir, "prompts"));
|
|
483
502
|
ensureDir(join(configDir, "workflows"));
|
|
503
|
+
ensureDir(join(configDir, "email-templates"));
|
|
484
504
|
const state = {
|
|
485
505
|
appId: resolvedAppId,
|
|
486
506
|
serverUrl: getServerUrl(),
|
|
@@ -506,11 +526,12 @@ Directory Structure:
|
|
|
506
526
|
info(`Pulling configuration for app ${resolvedAppId}...`);
|
|
507
527
|
try {
|
|
508
528
|
// Fetch all data
|
|
509
|
-
const [settings, integrationsResult, promptsResult, workflowsResult] = await Promise.all([
|
|
529
|
+
const [settings, integrationsResult, promptsResult, workflowsResult, emailTemplatesResult] = await Promise.all([
|
|
510
530
|
client.getAppSettings(resolvedAppId).catch(() => null),
|
|
511
531
|
client.listIntegrations(resolvedAppId, { limit: 100 }),
|
|
512
532
|
client.listPrompts(resolvedAppId, { limit: 100 }),
|
|
513
533
|
client.listWorkflows(resolvedAppId, { limit: 100 }),
|
|
534
|
+
client.listEmailTemplates(resolvedAppId).catch(() => ({ templates: [] })),
|
|
514
535
|
]);
|
|
515
536
|
// Fetch details for each entity
|
|
516
537
|
const integrations = await Promise.all(integrationsResult.items.map((i) => client.getIntegration(resolvedAppId, i.integrationId)));
|
|
@@ -536,8 +557,11 @@ Directory Structure:
|
|
|
536
557
|
}
|
|
537
558
|
return workflowData;
|
|
538
559
|
}));
|
|
560
|
+
// Fetch full details for email template overrides
|
|
561
|
+
const emailTemplateOverrides = emailTemplatesResult.templates.filter((t) => t.hasOverride);
|
|
562
|
+
const emailTemplates = await Promise.all(emailTemplateOverrides.map((t) => client.getEmailTemplate(resolvedAppId, t.emailType).catch(() => null))).then((results) => results.filter(Boolean));
|
|
539
563
|
if (options.json) {
|
|
540
|
-
json({ settings, integrations, prompts, workflows });
|
|
564
|
+
json({ settings, integrations, prompts, workflows, emailTemplates });
|
|
541
565
|
return;
|
|
542
566
|
}
|
|
543
567
|
// Ensure directories exist
|
|
@@ -545,6 +569,7 @@ Directory Structure:
|
|
|
545
569
|
ensureDir(join(configDir, "integrations"));
|
|
546
570
|
ensureDir(join(configDir, "prompts"));
|
|
547
571
|
ensureDir(join(configDir, "workflows"));
|
|
572
|
+
ensureDir(join(configDir, "email-templates"));
|
|
548
573
|
// Write app settings
|
|
549
574
|
if (settings) {
|
|
550
575
|
writeFileSync(join(configDir, "app.toml"), serializeAppSettings(settings));
|
|
@@ -584,6 +609,30 @@ Directory Structure:
|
|
|
584
609
|
};
|
|
585
610
|
info(` Wrote workflows/${filename}`);
|
|
586
611
|
}
|
|
612
|
+
// Write email templates
|
|
613
|
+
const emailTemplateEntities = {};
|
|
614
|
+
for (const template of emailTemplates) {
|
|
615
|
+
const filename = `${template.emailType}.toml`;
|
|
616
|
+
writeFileSync(join(configDir, "email-templates", filename), serializeEmailTemplate(template));
|
|
617
|
+
emailTemplateEntities[template.emailType] = {
|
|
618
|
+
id: template.emailType,
|
|
619
|
+
modifiedAt: template.override?.modifiedAt || new Date().toISOString(),
|
|
620
|
+
};
|
|
621
|
+
info(` Wrote email-templates/${filename}`);
|
|
622
|
+
}
|
|
623
|
+
// Clean up local email template files for remotely-deleted overrides
|
|
624
|
+
const emailTemplatesDir = join(configDir, "email-templates");
|
|
625
|
+
if (existsSync(emailTemplatesDir)) {
|
|
626
|
+
const pulledTypes = new Set(emailTemplates.map((t) => t.emailType));
|
|
627
|
+
const localFiles = readdirSync(emailTemplatesDir).filter((f) => f.endsWith(".toml"));
|
|
628
|
+
for (const file of localFiles) {
|
|
629
|
+
const localType = basename(file, ".toml");
|
|
630
|
+
if (!pulledTypes.has(localType)) {
|
|
631
|
+
unlinkSync(join(emailTemplatesDir, file));
|
|
632
|
+
info(` Removed email-templates/${file} (override deleted on server)`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
587
636
|
// Pull test cases for prompts and workflows
|
|
588
637
|
const testCaseEntities = {};
|
|
589
638
|
let totalTestCases = 0;
|
|
@@ -611,6 +660,7 @@ Directory Structure:
|
|
|
611
660
|
integrations: integrationEntities,
|
|
612
661
|
prompts: promptEntities,
|
|
613
662
|
workflows: workflowEntities,
|
|
663
|
+
emailTemplates: Object.keys(emailTemplateEntities).length > 0 ? emailTemplateEntities : undefined,
|
|
614
664
|
testCases: Object.keys(testCaseEntities).length > 0 ? testCaseEntities : undefined,
|
|
615
665
|
},
|
|
616
666
|
};
|
|
@@ -620,6 +670,7 @@ Directory Structure:
|
|
|
620
670
|
keyValue("Integrations", integrations.length);
|
|
621
671
|
keyValue("Prompts", prompts.length);
|
|
622
672
|
keyValue("Workflows", workflows.length);
|
|
673
|
+
keyValue("Email Templates", emailTemplates.length);
|
|
623
674
|
keyValue("Test Cases", totalTestCases);
|
|
624
675
|
}
|
|
625
676
|
catch (err) {
|
|
@@ -919,6 +970,42 @@ Directory Structure:
|
|
|
919
970
|
}
|
|
920
971
|
}
|
|
921
972
|
}
|
|
973
|
+
// Process email templates
|
|
974
|
+
const emailTemplatesDir = join(configDir, "email-templates");
|
|
975
|
+
if (existsSync(emailTemplatesDir)) {
|
|
976
|
+
const files = readdirSync(emailTemplatesDir).filter((f) => f.endsWith(".toml"));
|
|
977
|
+
for (const file of files) {
|
|
978
|
+
const filePath = join(emailTemplatesDir, file);
|
|
979
|
+
const tomlData = parseTomlFile(filePath);
|
|
980
|
+
const template = tomlData.template || {};
|
|
981
|
+
const emailType = template.emailType || basename(file, ".toml");
|
|
982
|
+
const payload = {
|
|
983
|
+
subject: template.subject || "",
|
|
984
|
+
htmlBody: template.htmlBody || "",
|
|
985
|
+
textBody: template.textBody || "",
|
|
986
|
+
};
|
|
987
|
+
changes.push({ type: "email-template", action: "update", key: emailType });
|
|
988
|
+
if (!options.dryRun) {
|
|
989
|
+
try {
|
|
990
|
+
const result = await client.upsertEmailTemplate(resolvedAppId, emailType, payload);
|
|
991
|
+
info(` Updated email template: ${emailType}`);
|
|
992
|
+
// Update sync state
|
|
993
|
+
if (syncState) {
|
|
994
|
+
if (!syncState.entities.emailTemplates) {
|
|
995
|
+
syncState.entities.emailTemplates = {};
|
|
996
|
+
}
|
|
997
|
+
syncState.entities.emailTemplates[emailType] = {
|
|
998
|
+
id: emailType,
|
|
999
|
+
modifiedAt: result?.modifiedAt || new Date().toISOString(),
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
catch (err) {
|
|
1004
|
+
warn(` Failed to update email template ${emailType}: ${err.message}`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
922
1009
|
// Push test cases for prompts and workflows
|
|
923
1010
|
const promptsDir2 = join(configDir, "prompts");
|
|
924
1011
|
if (existsSync(promptsDir2)) {
|
|
@@ -1016,18 +1103,23 @@ Directory Structure:
|
|
|
1016
1103
|
info(`Comparing local configuration with app ${resolvedAppId}...`);
|
|
1017
1104
|
try {
|
|
1018
1105
|
// Fetch remote state
|
|
1019
|
-
const [integrationsResult, promptsResult, workflowsResult] = await Promise.all([
|
|
1106
|
+
const [integrationsResult, promptsResult, workflowsResult, emailTemplatesResult] = await Promise.all([
|
|
1020
1107
|
client.listIntegrations(resolvedAppId, { limit: 100 }),
|
|
1021
1108
|
client.listPrompts(resolvedAppId, { limit: 100 }),
|
|
1022
1109
|
client.listWorkflows(resolvedAppId, { limit: 100 }),
|
|
1110
|
+
client.listEmailTemplates(resolvedAppId).catch(() => ({ templates: [] })),
|
|
1023
1111
|
]);
|
|
1024
1112
|
const remoteIntegrations = new Set(integrationsResult.items.map((i) => i.integrationKey));
|
|
1025
1113
|
const remotePrompts = new Set(promptsResult.items.map((p) => p.promptKey));
|
|
1026
1114
|
const remoteWorkflows = new Set(workflowsResult.items.map((w) => w.workflowKey));
|
|
1115
|
+
const remoteEmailTemplates = new Set((emailTemplatesResult.templates || [])
|
|
1116
|
+
.filter((t) => t.hasOverride)
|
|
1117
|
+
.map((t) => t.emailType));
|
|
1027
1118
|
// Get local files
|
|
1028
1119
|
const localIntegrations = new Set();
|
|
1029
1120
|
const localPrompts = new Set();
|
|
1030
1121
|
const localWorkflows = new Set();
|
|
1122
|
+
const localEmailTemplates = new Set();
|
|
1031
1123
|
const integrationsDir = join(configDir, "integrations");
|
|
1032
1124
|
if (existsSync(integrationsDir)) {
|
|
1033
1125
|
for (const file of readdirSync(integrationsDir).filter((f) => f.endsWith(".toml"))) {
|
|
@@ -1052,6 +1144,14 @@ Directory Structure:
|
|
|
1052
1144
|
localWorkflows.add(key);
|
|
1053
1145
|
}
|
|
1054
1146
|
}
|
|
1147
|
+
const emailTemplatesDirPath = join(configDir, "email-templates");
|
|
1148
|
+
if (existsSync(emailTemplatesDirPath)) {
|
|
1149
|
+
for (const file of readdirSync(emailTemplatesDirPath).filter((f) => f.endsWith(".toml"))) {
|
|
1150
|
+
const tomlData = parseTomlFile(join(emailTemplatesDirPath, file));
|
|
1151
|
+
const emailType = tomlData.template?.emailType || basename(file, ".toml");
|
|
1152
|
+
localEmailTemplates.add(emailType);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1055
1155
|
// Compare
|
|
1056
1156
|
const differences = [];
|
|
1057
1157
|
// Integrations
|
|
@@ -1096,6 +1196,20 @@ Directory Structure:
|
|
|
1096
1196
|
differences.push({ type: "workflow", key, status: "remote only" });
|
|
1097
1197
|
}
|
|
1098
1198
|
}
|
|
1199
|
+
// Email Templates
|
|
1200
|
+
for (const key of localEmailTemplates) {
|
|
1201
|
+
if (!remoteEmailTemplates.has(key)) {
|
|
1202
|
+
differences.push({ type: "email-template", key, status: "local only" });
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
differences.push({ type: "email-template", key, status: "exists" });
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
for (const key of remoteEmailTemplates) {
|
|
1209
|
+
if (!localEmailTemplates.has(key)) {
|
|
1210
|
+
differences.push({ type: "email-template", key, status: "remote only" });
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1099
1213
|
// Compare test cases for synced prompts and workflows
|
|
1100
1214
|
const testCaseDiffs = [];
|
|
1101
1215
|
// Helper to compare test cases for a block
|