@eide/foir-cli 0.1.35 → 0.1.37
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/cli.js +7467 -42
- package/dist/config/types.d.ts +7 -6
- package/dist/config/types.js +6 -14
- package/dist/generated-CPsQ9jfu.d.ts +195 -0
- package/dist/lib/extension-helpers.d.ts +10 -7
- package/dist/lib/extension-helpers.js +15 -85
- package/dist/lib/hook-helpers.d.ts +9 -6
- package/dist/lib/hook-helpers.js +12 -105
- package/dist/lib/seed-helpers.d.ts +10 -8
- package/dist/lib/seed-helpers.js +18 -124
- package/dist/schema.graphql +6062 -0
- package/package.json +9 -8
- package/dist/auth/credentials.d.ts +0 -31
- package/dist/auth/credentials.d.ts.map +0 -1
- package/dist/auth/credentials.js +0 -139
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/codegen/fetch-customer-profile-schema.d.ts +0 -12
- package/dist/codegen/fetch-customer-profile-schema.d.ts.map +0 -1
- package/dist/codegen/fetch-customer-profile-schema.js +0 -21
- package/dist/codegen/fetch-models.d.ts +0 -58
- package/dist/codegen/fetch-models.d.ts.map +0 -1
- package/dist/codegen/fetch-models.js +0 -96
- package/dist/codegen/field-mapping.d.ts +0 -34
- package/dist/codegen/field-mapping.d.ts.map +0 -1
- package/dist/codegen/field-mapping.js +0 -250
- package/dist/codegen/generators/config.d.ts +0 -5
- package/dist/codegen/generators/config.d.ts.map +0 -1
- package/dist/codegen/generators/config.js +0 -82
- package/dist/codegen/generators/customer-profile-documents.d.ts +0 -5
- package/dist/codegen/generators/customer-profile-documents.d.ts.map +0 -1
- package/dist/codegen/generators/customer-profile-documents.js +0 -42
- package/dist/codegen/generators/customer-profile-hooks.d.ts +0 -5
- package/dist/codegen/generators/customer-profile-hooks.d.ts.map +0 -1
- package/dist/codegen/generators/customer-profile-hooks.js +0 -78
- package/dist/codegen/generators/customer-profile-loaders.d.ts +0 -5
- package/dist/codegen/generators/customer-profile-loaders.d.ts.map +0 -1
- package/dist/codegen/generators/customer-profile-loaders.js +0 -67
- package/dist/codegen/generators/customer-profile-operations.d.ts +0 -5
- package/dist/codegen/generators/customer-profile-operations.d.ts.map +0 -1
- package/dist/codegen/generators/customer-profile-operations.js +0 -126
- package/dist/codegen/generators/customer-profile-types.d.ts +0 -6
- package/dist/codegen/generators/customer-profile-types.d.ts.map +0 -1
- package/dist/codegen/generators/customer-profile-types.js +0 -45
- package/dist/codegen/generators/documents.d.ts +0 -12
- package/dist/codegen/generators/documents.d.ts.map +0 -1
- package/dist/codegen/generators/documents.js +0 -178
- package/dist/codegen/generators/field-types.d.ts +0 -5
- package/dist/codegen/generators/field-types.d.ts.map +0 -1
- package/dist/codegen/generators/field-types.js +0 -346
- package/dist/codegen/generators/model-index.d.ts +0 -6
- package/dist/codegen/generators/model-index.d.ts.map +0 -1
- package/dist/codegen/generators/model-index.js +0 -26
- package/dist/codegen/generators/model-types.d.ts +0 -12
- package/dist/codegen/generators/model-types.d.ts.map +0 -1
- package/dist/codegen/generators/model-types.js +0 -177
- package/dist/codegen/generators/public-schema-content.d.ts +0 -14
- package/dist/codegen/generators/public-schema-content.d.ts.map +0 -1
- package/dist/codegen/generators/public-schema-content.js +0 -22
- package/dist/codegen/generators/react-hooks-index.d.ts +0 -6
- package/dist/codegen/generators/react-hooks-index.d.ts.map +0 -1
- package/dist/codegen/generators/react-hooks-index.js +0 -20
- package/dist/codegen/generators/react-hooks.d.ts +0 -7
- package/dist/codegen/generators/react-hooks.d.ts.map +0 -1
- package/dist/codegen/generators/react-hooks.js +0 -139
- package/dist/codegen/generators/remix-loaders-index.d.ts +0 -6
- package/dist/codegen/generators/remix-loaders-index.d.ts.map +0 -1
- package/dist/codegen/generators/remix-loaders-index.js +0 -20
- package/dist/codegen/generators/remix-loaders.d.ts +0 -7
- package/dist/codegen/generators/remix-loaders.d.ts.map +0 -1
- package/dist/codegen/generators/remix-loaders.js +0 -107
- package/dist/codegen/generators/static-documents.d.ts +0 -14
- package/dist/codegen/generators/static-documents.d.ts.map +0 -1
- package/dist/codegen/generators/static-documents.js +0 -771
- package/dist/codegen/generators/swift-customer-profile.d.ts +0 -9
- package/dist/codegen/generators/swift-customer-profile.d.ts.map +0 -1
- package/dist/codegen/generators/swift-customer-profile.js +0 -152
- package/dist/codegen/generators/swift-field-types.d.ts +0 -5
- package/dist/codegen/generators/swift-field-types.d.ts.map +0 -1
- package/dist/codegen/generators/swift-field-types.js +0 -151
- package/dist/codegen/generators/swift-model-keys.d.ts +0 -6
- package/dist/codegen/generators/swift-model-keys.d.ts.map +0 -1
- package/dist/codegen/generators/swift-model-keys.js +0 -25
- package/dist/codegen/generators/swift-types.d.ts +0 -13
- package/dist/codegen/generators/swift-types.d.ts.map +0 -1
- package/dist/codegen/generators/swift-types.js +0 -188
- package/dist/codegen/generators/typed-operations-common.d.ts +0 -6
- package/dist/codegen/generators/typed-operations-common.d.ts.map +0 -1
- package/dist/codegen/generators/typed-operations-common.js +0 -84
- package/dist/codegen/generators/typed-operations-index.d.ts +0 -6
- package/dist/codegen/generators/typed-operations-index.d.ts.map +0 -1
- package/dist/codegen/generators/typed-operations-index.js +0 -22
- package/dist/codegen/generators/typed-operations.d.ts +0 -11
- package/dist/codegen/generators/typed-operations.d.ts.map +0 -1
- package/dist/codegen/generators/typed-operations.js +0 -263
- package/dist/codegen/swift-field-mapping.d.ts +0 -30
- package/dist/codegen/swift-field-mapping.d.ts.map +0 -1
- package/dist/codegen/swift-field-mapping.js +0 -150
- package/dist/codegen/write-files.d.ts +0 -15
- package/dist/codegen/write-files.d.ts.map +0 -1
- package/dist/codegen/write-files.js +0 -36
- package/dist/commands/api-keys.d.ts +0 -4
- package/dist/commands/api-keys.d.ts.map +0 -1
- package/dist/commands/api-keys.js +0 -129
- package/dist/commands/auth-config.d.ts +0 -4
- package/dist/commands/auth-config.d.ts.map +0 -1
- package/dist/commands/auth-config.js +0 -38
- package/dist/commands/auth-providers.d.ts +0 -4
- package/dist/commands/auth-providers.d.ts.map +0 -1
- package/dist/commands/auth-providers.js +0 -207
- package/dist/commands/context.d.ts +0 -4
- package/dist/commands/context.d.ts.map +0 -1
- package/dist/commands/context.js +0 -91
- package/dist/commands/create-extension.d.ts +0 -4
- package/dist/commands/create-extension.d.ts.map +0 -1
- package/dist/commands/create-extension.js +0 -60
- package/dist/commands/customer-profiles.d.ts +0 -4
- package/dist/commands/customer-profiles.d.ts.map +0 -1
- package/dist/commands/customer-profiles.js +0 -99
- package/dist/commands/customers.d.ts +0 -4
- package/dist/commands/customers.d.ts.map +0 -1
- package/dist/commands/customers.js +0 -126
- package/dist/commands/embeddings.d.ts +0 -4
- package/dist/commands/embeddings.d.ts.map +0 -1
- package/dist/commands/embeddings.js +0 -145
- package/dist/commands/experiments.d.ts +0 -4
- package/dist/commands/experiments.d.ts.map +0 -1
- package/dist/commands/experiments.js +0 -196
- package/dist/commands/extensions.d.ts +0 -4
- package/dist/commands/extensions.d.ts.map +0 -1
- package/dist/commands/extensions.js +0 -210
- package/dist/commands/files.d.ts +0 -4
- package/dist/commands/files.d.ts.map +0 -1
- package/dist/commands/files.js +0 -143
- package/dist/commands/hooks.d.ts +0 -4
- package/dist/commands/hooks.d.ts.map +0 -1
- package/dist/commands/hooks.js +0 -228
- package/dist/commands/locales.d.ts +0 -4
- package/dist/commands/locales.d.ts.map +0 -1
- package/dist/commands/locales.js +0 -140
- package/dist/commands/login.d.ts +0 -4
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -124
- package/dist/commands/logout.d.ts +0 -4
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js +0 -16
- package/dist/commands/media.d.ts +0 -4
- package/dist/commands/media.d.ts.map +0 -1
- package/dist/commands/media.js +0 -44
- package/dist/commands/models.d.ts +0 -4
- package/dist/commands/models.d.ts.map +0 -1
- package/dist/commands/models.js +0 -153
- package/dist/commands/notes.d.ts +0 -4
- package/dist/commands/notes.d.ts.map +0 -1
- package/dist/commands/notes.js +0 -132
- package/dist/commands/notifications.d.ts +0 -4
- package/dist/commands/notifications.d.ts.map +0 -1
- package/dist/commands/notifications.js +0 -73
- package/dist/commands/operations.d.ts +0 -4
- package/dist/commands/operations.d.ts.map +0 -1
- package/dist/commands/operations.js +0 -341
- package/dist/commands/playground.d.ts +0 -4
- package/dist/commands/playground.d.ts.map +0 -1
- package/dist/commands/playground.js +0 -270
- package/dist/commands/pull.d.ts +0 -4
- package/dist/commands/pull.d.ts.map +0 -1
- package/dist/commands/pull.js +0 -300
- package/dist/commands/records.d.ts +0 -4
- package/dist/commands/records.d.ts.map +0 -1
- package/dist/commands/records.js +0 -314
- package/dist/commands/register-commands.d.ts +0 -7
- package/dist/commands/register-commands.d.ts.map +0 -1
- package/dist/commands/register-commands.js +0 -259
- package/dist/commands/schedules.d.ts +0 -4
- package/dist/commands/schedules.d.ts.map +0 -1
- package/dist/commands/schedules.js +0 -155
- package/dist/commands/search.d.ts +0 -4
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js +0 -60
- package/dist/commands/segments.d.ts +0 -4
- package/dist/commands/segments.d.ts.map +0 -1
- package/dist/commands/segments.js +0 -155
- package/dist/commands/select-project.d.ts +0 -4
- package/dist/commands/select-project.d.ts.map +0 -1
- package/dist/commands/select-project.js +0 -144
- package/dist/commands/settings.d.ts +0 -4
- package/dist/commands/settings.d.ts.map +0 -1
- package/dist/commands/settings.js +0 -115
- package/dist/commands/variant-catalog.d.ts +0 -4
- package/dist/commands/variant-catalog.d.ts.map +0 -1
- package/dist/commands/variant-catalog.js +0 -118
- package/dist/commands/whoami.d.ts +0 -4
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js +0 -51
- package/dist/config/pull-config.d.ts +0 -33
- package/dist/config/pull-config.d.ts.map +0 -1
- package/dist/config/pull-config.js +0 -119
- package/dist/config/types.d.ts.map +0 -1
- package/dist/graphql/generated.d.ts +0 -6074
- package/dist/graphql/generated.d.ts.map +0 -1
- package/dist/graphql/generated.js +0 -355
- package/dist/lib/client.d.ts +0 -18
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/client.js +0 -64
- package/dist/lib/config-loader.d.ts +0 -28
- package/dist/lib/config-loader.d.ts.map +0 -1
- package/dist/lib/config-loader.js +0 -49
- package/dist/lib/config.d.ts +0 -12
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -8
- package/dist/lib/errors.d.ts +0 -6
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js +0 -76
- package/dist/lib/extension-helpers.d.ts.map +0 -1
- package/dist/lib/hook-helpers.d.ts.map +0 -1
- package/dist/lib/input.d.ts +0 -38
- package/dist/lib/input.d.ts.map +0 -1
- package/dist/lib/input.js +0 -108
- package/dist/lib/output.d.ts +0 -31
- package/dist/lib/output.d.ts.map +0 -1
- package/dist/lib/output.js +0 -107
- package/dist/lib/seed-helpers.d.ts.map +0 -1
- package/dist/scaffold/package-manager.d.ts +0 -12
- package/dist/scaffold/package-manager.d.ts.map +0 -1
- package/dist/scaffold/package-manager.js +0 -51
- package/dist/scaffold/scaffold.d.ts +0 -4
- package/dist/scaffold/scaffold.d.ts.map +0 -1
- package/dist/scaffold/scaffold.js +0 -462
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { withErrorHandler } from '../lib/errors.js';
|
|
2
|
-
import { createClient } from '../lib/client.js';
|
|
3
|
-
import { formatOutput, formatList, success } from '../lib/output.js';
|
|
4
|
-
import { parseInputData, isUUID } from '../lib/input.js';
|
|
5
|
-
import { ListExtensionsDocument, GetExtensionDocument, GetExtensionByKeyDocument, RegisterExtensionDocument, TriggerExtensionSyncDocument, InstallExtensionDocument, UnregisterExtensionDocument, // Still used by uninstall command
|
|
6
|
-
} from '../graphql/generated.js';
|
|
7
|
-
export function registerExtensionsCommands(program, globalOpts) {
|
|
8
|
-
const extensions = program
|
|
9
|
-
.command('extensions')
|
|
10
|
-
.description('Manage extensions');
|
|
11
|
-
// list
|
|
12
|
-
extensions
|
|
13
|
-
.command('list')
|
|
14
|
-
.description('List extensions')
|
|
15
|
-
.option('--type <type>', 'Filter by extension type')
|
|
16
|
-
.option('--enabled', 'Only enabled extensions')
|
|
17
|
-
.option('--limit <n>', 'Max results', '50')
|
|
18
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
19
|
-
const opts = globalOpts();
|
|
20
|
-
const client = await createClient(opts);
|
|
21
|
-
const data = await client.request(ListExtensionsDocument, {
|
|
22
|
-
extensionType: cmdOpts.type,
|
|
23
|
-
enabled: cmdOpts.enabled ? true : undefined,
|
|
24
|
-
limit: parseInt(String(cmdOpts.limit ?? '50'), 10),
|
|
25
|
-
});
|
|
26
|
-
formatList(data.extensions, opts, {
|
|
27
|
-
columns: [
|
|
28
|
-
{ key: 'id', header: 'ID', width: 28 },
|
|
29
|
-
{ key: 'key', header: 'Key', width: 20 },
|
|
30
|
-
{ key: 'name', header: 'Name', width: 24 },
|
|
31
|
-
{ key: 'extensionType', header: 'Type', width: 14 },
|
|
32
|
-
{
|
|
33
|
-
key: 'enabled',
|
|
34
|
-
header: 'Enabled',
|
|
35
|
-
width: 8,
|
|
36
|
-
format: (v) => (v ? 'yes' : 'no'),
|
|
37
|
-
},
|
|
38
|
-
{ key: 'syncStatus', header: 'Sync', width: 10 },
|
|
39
|
-
],
|
|
40
|
-
});
|
|
41
|
-
}));
|
|
42
|
-
// get
|
|
43
|
-
extensions
|
|
44
|
-
.command('get <idOrKey>')
|
|
45
|
-
.description('Get an extension by ID or key')
|
|
46
|
-
.action(withErrorHandler(globalOpts, async (idOrKey) => {
|
|
47
|
-
const opts = globalOpts();
|
|
48
|
-
const client = await createClient(opts);
|
|
49
|
-
let result;
|
|
50
|
-
if (isUUID(idOrKey)) {
|
|
51
|
-
const data = await client.request(GetExtensionDocument, {
|
|
52
|
-
id: idOrKey,
|
|
53
|
-
});
|
|
54
|
-
result = data.extension;
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
const data = await client.request(GetExtensionByKeyDocument, {
|
|
58
|
-
key: idOrKey,
|
|
59
|
-
});
|
|
60
|
-
result = data.extensionByKey;
|
|
61
|
-
}
|
|
62
|
-
if (!result)
|
|
63
|
-
throw new Error(`Extension "${idOrKey}" not found.`);
|
|
64
|
-
formatOutput(result, opts);
|
|
65
|
-
}));
|
|
66
|
-
// register
|
|
67
|
-
extensions
|
|
68
|
-
.command('register')
|
|
69
|
-
.description('Register a new extension')
|
|
70
|
-
.option('-d, --data <json>', 'Extension data as JSON')
|
|
71
|
-
.option('-f, --file <path>', 'Read data from file')
|
|
72
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
73
|
-
const opts = globalOpts();
|
|
74
|
-
const client = await createClient(opts);
|
|
75
|
-
const input = await parseInputData(cmdOpts);
|
|
76
|
-
const data = await client.request(RegisterExtensionDocument, {
|
|
77
|
-
input,
|
|
78
|
-
});
|
|
79
|
-
formatOutput(data.registerExtension, opts);
|
|
80
|
-
if (!(opts.json || opts.jsonl || opts.quiet))
|
|
81
|
-
success(`Registered extension ${data.registerExtension.key}`);
|
|
82
|
-
}));
|
|
83
|
-
// install
|
|
84
|
-
extensions
|
|
85
|
-
.command('install')
|
|
86
|
-
.description('Install (or update) an extension with full provisioning')
|
|
87
|
-
.option('-d, --data <json>', 'Extension manifest as JSON')
|
|
88
|
-
.option('-f, --file <path>', 'Read manifest from file (.ts, .js, or .json)')
|
|
89
|
-
.option('-k, --key <key>', 'Extension key (overrides manifest)')
|
|
90
|
-
.option('--force', 'Uninstall existing extension before installing (fresh install)')
|
|
91
|
-
.addHelpText('after', `
|
|
92
|
-
Examples:
|
|
93
|
-
$ foir extensions install -f install.config.ts
|
|
94
|
-
$ foir extensions install -f install.json --force
|
|
95
|
-
$ foir extensions install -d '{"key":"my-ext","name":"My Extension",...}'
|
|
96
|
-
`)
|
|
97
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
98
|
-
const opts = globalOpts();
|
|
99
|
-
const client = await createClient(opts);
|
|
100
|
-
const input = await parseInputData(cmdOpts);
|
|
101
|
-
// Allow --key to override the manifest key
|
|
102
|
-
if (cmdOpts.key) {
|
|
103
|
-
input.key = cmdOpts.key;
|
|
104
|
-
}
|
|
105
|
-
// Add force flag to input if --force is specified
|
|
106
|
-
if (cmdOpts.force) {
|
|
107
|
-
input.force = true;
|
|
108
|
-
}
|
|
109
|
-
const data = await client.request(InstallExtensionDocument, {
|
|
110
|
-
input,
|
|
111
|
-
});
|
|
112
|
-
const result = data.installExtension;
|
|
113
|
-
if (opts.json || opts.jsonl || opts.quiet) {
|
|
114
|
-
formatOutput(result, opts);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (result.isUpdate) {
|
|
118
|
-
success(`Updated extension "${result.extensionKey}"`);
|
|
119
|
-
console.log('');
|
|
120
|
-
console.log(` Models: ${result.modelsCreated} created, ${result.modelsUpdated} updated`);
|
|
121
|
-
console.log(` Operations: ${result.operationsCreated} created, ${result.operationsUpdated} updated`);
|
|
122
|
-
console.log(` Extension: ${result.extensionId}`);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
success(`Installed extension "${result.extensionKey}"`);
|
|
126
|
-
console.log('');
|
|
127
|
-
console.log(` Models: ${result.modelsCreated} created`);
|
|
128
|
-
console.log(` Operations: ${result.operationsCreated} created`);
|
|
129
|
-
console.log(` Extension: ${result.extensionId}`);
|
|
130
|
-
if (result.credentials) {
|
|
131
|
-
console.log('');
|
|
132
|
-
console.log(' ┌─────────────────────────────────────────────────┐');
|
|
133
|
-
console.log(' │ CREDENTIALS (save these — shown only once!) │');
|
|
134
|
-
console.log(' ├─────────────────────────────────────────────────┤');
|
|
135
|
-
console.log(` │ API Key: ${result.credentials.platformApiKey}`);
|
|
136
|
-
console.log(` │ Editor Key: ${result.credentials.platformEditorKey}`);
|
|
137
|
-
console.log(` │ Webhook: ${result.credentials.webhookSecret}`);
|
|
138
|
-
console.log(' └─────────────────────────────────────────────────┘');
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}));
|
|
142
|
-
// uninstall
|
|
143
|
-
extensions
|
|
144
|
-
.command('uninstall <keyOrId>')
|
|
145
|
-
.description('Uninstall an extension by key or ID')
|
|
146
|
-
.addHelpText('after', `
|
|
147
|
-
Examples:
|
|
148
|
-
$ foir extensions uninstall tilly
|
|
149
|
-
$ foir extensions uninstall c7a3f8b0-1234-5678-90ab-cdef12345678
|
|
150
|
-
`)
|
|
151
|
-
.action(withErrorHandler(globalOpts, async (keyOrId) => {
|
|
152
|
-
const opts = globalOpts();
|
|
153
|
-
const client = await createClient(opts);
|
|
154
|
-
// Resolve key to ID if needed
|
|
155
|
-
let extensionId;
|
|
156
|
-
let extensionKey;
|
|
157
|
-
if (isUUID(keyOrId)) {
|
|
158
|
-
extensionId = keyOrId;
|
|
159
|
-
const data = await client.request(GetExtensionDocument, {
|
|
160
|
-
id: keyOrId,
|
|
161
|
-
});
|
|
162
|
-
if (!data.extension) {
|
|
163
|
-
throw new Error(`Extension "${keyOrId}" not found.`);
|
|
164
|
-
}
|
|
165
|
-
extensionKey = data.extension.key;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
const data = await client.request(GetExtensionByKeyDocument, {
|
|
169
|
-
key: keyOrId,
|
|
170
|
-
});
|
|
171
|
-
if (!data.extensionByKey) {
|
|
172
|
-
throw new Error(`Extension "${keyOrId}" not found.`);
|
|
173
|
-
}
|
|
174
|
-
extensionId = data.extensionByKey.id;
|
|
175
|
-
extensionKey = data.extensionByKey.key;
|
|
176
|
-
}
|
|
177
|
-
// Unregister the extension
|
|
178
|
-
await client.request(UnregisterExtensionDocument, { id: extensionId });
|
|
179
|
-
if (opts.json || opts.jsonl) {
|
|
180
|
-
formatOutput({ success: true, extensionId, extensionKey }, opts);
|
|
181
|
-
}
|
|
182
|
-
else if (opts.quiet) {
|
|
183
|
-
console.log(extensionId);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
success(`Uninstalled extension "${extensionKey}"`);
|
|
187
|
-
console.log(` Extension ID: ${extensionId}`);
|
|
188
|
-
}
|
|
189
|
-
}));
|
|
190
|
-
// sync
|
|
191
|
-
extensions
|
|
192
|
-
.command('sync <id>')
|
|
193
|
-
.description('Trigger data sync for an extension')
|
|
194
|
-
.action(withErrorHandler(globalOpts, async (id) => {
|
|
195
|
-
const opts = globalOpts();
|
|
196
|
-
const client = await createClient(opts);
|
|
197
|
-
const data = await client.request(TriggerExtensionSyncDocument, {
|
|
198
|
-
extensionId: id,
|
|
199
|
-
});
|
|
200
|
-
if (opts.json || opts.jsonl) {
|
|
201
|
-
formatOutput(data.triggerExtensionSync, opts);
|
|
202
|
-
}
|
|
203
|
-
else if (data.triggerExtensionSync.success) {
|
|
204
|
-
success('Extension sync triggered');
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
throw new Error(data.triggerExtensionSync.error ?? 'Sync failed');
|
|
208
|
-
}
|
|
209
|
-
}));
|
|
210
|
-
}
|
package/dist/commands/files.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/commands/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AActD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CA6JN"}
|
package/dist/commands/files.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { withErrorHandler } from '../lib/errors.js';
|
|
2
|
-
import { createClient } from '../lib/client.js';
|
|
3
|
-
import { formatOutput, formatList, timeAgo, success } from '../lib/output.js';
|
|
4
|
-
import { confirmAction } from '../lib/input.js';
|
|
5
|
-
import { ListFilesDocument, GetFileDocument, FileStorageUsageDocument, UpdateFileDocument, UpdateFileMetadataDocument, DeleteFileDocument, } from '../graphql/generated.js';
|
|
6
|
-
export function registerFilesCommands(program, globalOpts) {
|
|
7
|
-
const files = program
|
|
8
|
-
.command('files')
|
|
9
|
-
.description('Manage files (for upload, use `foir media upload`)');
|
|
10
|
-
// list
|
|
11
|
-
files
|
|
12
|
-
.command('list')
|
|
13
|
-
.description('List files')
|
|
14
|
-
.option('--folder <folder>', 'Filter by folder')
|
|
15
|
-
.option('--mime-type <type>', 'Filter by MIME type')
|
|
16
|
-
.option('--search <term>', 'Search by filename')
|
|
17
|
-
.option('--limit <n>', 'Max results', '50')
|
|
18
|
-
.option('--offset <n>', 'Skip results', '0')
|
|
19
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
20
|
-
const opts = globalOpts();
|
|
21
|
-
const client = await createClient(opts);
|
|
22
|
-
const data = await client.request(ListFilesDocument, {
|
|
23
|
-
folder: cmdOpts.folder,
|
|
24
|
-
mimeType: cmdOpts.mimeType,
|
|
25
|
-
search: cmdOpts.search,
|
|
26
|
-
limit: parseInt(cmdOpts.limit ?? '50', 10),
|
|
27
|
-
offset: parseInt(cmdOpts.offset ?? '0', 10),
|
|
28
|
-
});
|
|
29
|
-
formatList(data.files.files, opts, {
|
|
30
|
-
columns: [
|
|
31
|
-
{ key: 'id', header: 'ID', width: 28 },
|
|
32
|
-
{ key: 'filename', header: 'Filename', width: 30 },
|
|
33
|
-
{ key: 'mimeType', header: 'Type', width: 16 },
|
|
34
|
-
{
|
|
35
|
-
key: 'size',
|
|
36
|
-
header: 'Size',
|
|
37
|
-
width: 10,
|
|
38
|
-
format: (v) => formatBytes(v),
|
|
39
|
-
},
|
|
40
|
-
{ key: 'folder', header: 'Folder', width: 14 },
|
|
41
|
-
{
|
|
42
|
-
key: 'createdAt',
|
|
43
|
-
header: 'Created',
|
|
44
|
-
width: 12,
|
|
45
|
-
format: (v) => timeAgo(v),
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
total: data.files.total,
|
|
49
|
-
});
|
|
50
|
-
}));
|
|
51
|
-
// get
|
|
52
|
-
files
|
|
53
|
-
.command('get <id>')
|
|
54
|
-
.description('Get a file by ID')
|
|
55
|
-
.action(withErrorHandler(globalOpts, async (id) => {
|
|
56
|
-
const opts = globalOpts();
|
|
57
|
-
const client = await createClient(opts);
|
|
58
|
-
const data = await client.request(GetFileDocument, { id });
|
|
59
|
-
if (!data.file)
|
|
60
|
-
throw new Error(`File "${id}" not found.`);
|
|
61
|
-
formatOutput(data.file, opts);
|
|
62
|
-
}));
|
|
63
|
-
// usage
|
|
64
|
-
files
|
|
65
|
-
.command('usage')
|
|
66
|
-
.description('Get storage usage statistics')
|
|
67
|
-
.action(withErrorHandler(globalOpts, async () => {
|
|
68
|
-
const opts = globalOpts();
|
|
69
|
-
const client = await createClient(opts);
|
|
70
|
-
const data = await client.request(FileStorageUsageDocument);
|
|
71
|
-
formatOutput(data.fileStorageUsage, opts);
|
|
72
|
-
}));
|
|
73
|
-
// update
|
|
74
|
-
files
|
|
75
|
-
.command('update <id>')
|
|
76
|
-
.description('Update file properties')
|
|
77
|
-
.option('--filename <name>', 'New filename')
|
|
78
|
-
.option('--folder <folder>', 'Move to folder')
|
|
79
|
-
.option('--tags <tags>', 'Comma-separated tags')
|
|
80
|
-
.action(withErrorHandler(globalOpts, async (id, cmdOpts) => {
|
|
81
|
-
const opts = globalOpts();
|
|
82
|
-
const client = await createClient(opts);
|
|
83
|
-
const vars = { id };
|
|
84
|
-
if (cmdOpts.filename)
|
|
85
|
-
vars.filename = cmdOpts.filename;
|
|
86
|
-
if (cmdOpts.folder)
|
|
87
|
-
vars.folder = cmdOpts.folder;
|
|
88
|
-
if (cmdOpts.tags)
|
|
89
|
-
vars.tags = cmdOpts.tags.split(',').map((t) => t.trim());
|
|
90
|
-
const data = await client.request(UpdateFileDocument, vars);
|
|
91
|
-
formatOutput(data.updateFile, opts);
|
|
92
|
-
if (!(opts.json || opts.jsonl || opts.quiet))
|
|
93
|
-
success(`Updated file ${id}`);
|
|
94
|
-
}));
|
|
95
|
-
// update-metadata
|
|
96
|
-
files
|
|
97
|
-
.command('update-metadata <id>')
|
|
98
|
-
.description('Update file metadata (alt text, caption, description)')
|
|
99
|
-
.option('--alt-text <text>', 'Alt text')
|
|
100
|
-
.option('--caption <text>', 'Caption')
|
|
101
|
-
.option('--description <text>', 'Description')
|
|
102
|
-
.action(withErrorHandler(globalOpts, async (id, cmdOpts) => {
|
|
103
|
-
const opts = globalOpts();
|
|
104
|
-
const client = await createClient(opts);
|
|
105
|
-
const data = await client.request(UpdateFileMetadataDocument, {
|
|
106
|
-
id,
|
|
107
|
-
altText: cmdOpts.altText,
|
|
108
|
-
caption: cmdOpts.caption,
|
|
109
|
-
description: cmdOpts.description,
|
|
110
|
-
});
|
|
111
|
-
formatOutput(data.updateFileMetadata, opts);
|
|
112
|
-
if (!(opts.json || opts.jsonl || opts.quiet))
|
|
113
|
-
success(`Updated metadata for file ${id}`);
|
|
114
|
-
}));
|
|
115
|
-
// delete
|
|
116
|
-
files
|
|
117
|
-
.command('delete <id>')
|
|
118
|
-
.description('Delete a file')
|
|
119
|
-
.option('--confirm', 'Skip confirmation prompt')
|
|
120
|
-
.action(withErrorHandler(globalOpts, async (id, cmdOpts) => {
|
|
121
|
-
const opts = globalOpts();
|
|
122
|
-
const confirmed = await confirmAction(`Delete file ${id}?`, {
|
|
123
|
-
confirm: !!cmdOpts.confirm,
|
|
124
|
-
});
|
|
125
|
-
if (!confirmed) {
|
|
126
|
-
console.log('Aborted.');
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const client = await createClient(opts);
|
|
130
|
-
await client.request(DeleteFileDocument, { id });
|
|
131
|
-
if (opts.json || opts.jsonl)
|
|
132
|
-
formatOutput({ deleted: true, id }, opts);
|
|
133
|
-
else
|
|
134
|
-
success(`Deleted file ${id}`);
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
function formatBytes(bytes) {
|
|
138
|
-
if (!bytes || bytes === 0)
|
|
139
|
-
return '0 B';
|
|
140
|
-
const units = ['B', 'KB', 'MB', 'GB'];
|
|
141
|
-
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
142
|
-
return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
143
|
-
}
|
package/dist/commands/hooks.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAgBtD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CAoRN"}
|
package/dist/commands/hooks.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { withErrorHandler } from '../lib/errors.js';
|
|
2
|
-
import { createClient } from '../lib/client.js';
|
|
3
|
-
import { formatOutput, formatList, timeAgo, success } from '../lib/output.js';
|
|
4
|
-
import { parseInputData, confirmAction } from '../lib/input.js';
|
|
5
|
-
import { ListHooksDocument, GetHookByKeyDocument, CreateHookDocument, UpdateHookDocument, DeleteHookDocument, HookDeliveriesDocument, RetryHookDeliveryDocument, TestHookDocument, } from '../graphql/generated.js';
|
|
6
|
-
export function registerHooksCommands(program, globalOpts) {
|
|
7
|
-
const hooks = program
|
|
8
|
-
.command('hooks')
|
|
9
|
-
.description('Manage event hooks — triggers that run operations or send notifications when platform events occur');
|
|
10
|
-
// list
|
|
11
|
-
hooks
|
|
12
|
-
.command('list')
|
|
13
|
-
.description('List hooks')
|
|
14
|
-
.option('--event <event>', 'Filter by event (e.g. RECORD_CREATED)')
|
|
15
|
-
.option('--active', 'Only active hooks')
|
|
16
|
-
.option('--limit <n>', 'Max results', '50')
|
|
17
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
18
|
-
const opts = globalOpts();
|
|
19
|
-
const client = await createClient(opts);
|
|
20
|
-
const data = await client.request(ListHooksDocument, {
|
|
21
|
-
event: cmdOpts.event,
|
|
22
|
-
isActive: cmdOpts.active ? true : undefined,
|
|
23
|
-
limit: parseInt(String(cmdOpts.limit ?? '50'), 10),
|
|
24
|
-
});
|
|
25
|
-
formatList(data.hooks.items, opts, {
|
|
26
|
-
columns: [
|
|
27
|
-
{ key: 'key', header: 'Key', width: 24 },
|
|
28
|
-
{ key: 'name', header: 'Name', width: 20 },
|
|
29
|
-
{ key: 'event', header: 'Event', width: 24 },
|
|
30
|
-
{
|
|
31
|
-
key: 'targetType',
|
|
32
|
-
header: 'Target',
|
|
33
|
-
width: 14,
|
|
34
|
-
format: (v) => v === 'notification' ? 'notification' : 'operation',
|
|
35
|
-
},
|
|
36
|
-
{ key: 'operationKey', header: 'Operation', width: 20 },
|
|
37
|
-
{
|
|
38
|
-
key: 'isActive',
|
|
39
|
-
header: 'Active',
|
|
40
|
-
width: 8,
|
|
41
|
-
format: (v) => (v ? 'yes' : 'no'),
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
key: 'updatedAt',
|
|
45
|
-
header: 'Updated',
|
|
46
|
-
width: 12,
|
|
47
|
-
format: (v) => timeAgo(v),
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
total: data.hooks.total,
|
|
51
|
-
});
|
|
52
|
-
}));
|
|
53
|
-
// get
|
|
54
|
-
hooks
|
|
55
|
-
.command('get <key>')
|
|
56
|
-
.description('Get a hook by key')
|
|
57
|
-
.action(withErrorHandler(globalOpts, async (key) => {
|
|
58
|
-
const opts = globalOpts();
|
|
59
|
-
const client = await createClient(opts);
|
|
60
|
-
const data = await client.request(GetHookByKeyDocument, { key });
|
|
61
|
-
if (!data.hookByKey)
|
|
62
|
-
throw new Error(`Hook "${key}" not found.`);
|
|
63
|
-
formatOutput(data.hookByKey, opts);
|
|
64
|
-
}));
|
|
65
|
-
// create
|
|
66
|
-
hooks
|
|
67
|
-
.command('create')
|
|
68
|
-
.description(`Create a hook
|
|
69
|
-
|
|
70
|
-
Required fields: key, name, event
|
|
71
|
-
Optional fields: targetType (operation|notification), operationKey, notificationConfig,
|
|
72
|
-
filter { modelKey, customerSegmentKey, condition }, isActive
|
|
73
|
-
|
|
74
|
-
Events: RECORD_CREATED, RECORD_UPDATED, RECORD_DELETED, RECORD_PUBLISHED,
|
|
75
|
-
RECORD_UNPUBLISHED, CUSTOMER_REGISTERED, CUSTOMER_DELETED,
|
|
76
|
-
CUSTOMER_PROFILE_UPDATED, EXTENSION_INITIALIZED, and more.
|
|
77
|
-
|
|
78
|
-
Conditions use rule expressions (JSON) to filter when the hook fires.
|
|
79
|
-
Example with condition:
|
|
80
|
-
foir hooks create -d '{
|
|
81
|
-
"key": "notify-pro-publish",
|
|
82
|
-
"name": "Notify on Pro Publish",
|
|
83
|
-
"event": "RECORD_PUBLISHED",
|
|
84
|
-
"targetType": "operation",
|
|
85
|
-
"operationKey": "my-webhook",
|
|
86
|
-
"filter": {
|
|
87
|
-
"modelKey": "blog_post",
|
|
88
|
-
"condition": {
|
|
89
|
-
"type": "condition",
|
|
90
|
-
"left": { "type": "context", "path": "customer.profile.plan" },
|
|
91
|
-
"operator": "in",
|
|
92
|
-
"right": { "type": "literal", "value": ["pro", "enterprise"] }
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}'`)
|
|
96
|
-
.option('-d, --data <json>', 'Hook data as JSON')
|
|
97
|
-
.option('-f, --file <path>', 'Read input from file')
|
|
98
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
99
|
-
const opts = globalOpts();
|
|
100
|
-
const client = await createClient(opts);
|
|
101
|
-
const input = await parseInputData(cmdOpts);
|
|
102
|
-
const data = await client.request(CreateHookDocument, { input });
|
|
103
|
-
formatOutput(data.createHook, opts);
|
|
104
|
-
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
105
|
-
success(`Hook "${data.createHook.key}" created`);
|
|
106
|
-
}
|
|
107
|
-
}));
|
|
108
|
-
// update
|
|
109
|
-
hooks
|
|
110
|
-
.command('update <id>')
|
|
111
|
-
.description(`Update a hook
|
|
112
|
-
|
|
113
|
-
All fields are optional. Supports: name, event, targetType, operationKey,
|
|
114
|
-
notificationConfig, filter (with condition), isActive.`)
|
|
115
|
-
.option('-d, --data <json>', 'Update data as JSON')
|
|
116
|
-
.option('-f, --file <path>', 'Read input from file')
|
|
117
|
-
.action(withErrorHandler(globalOpts, async (id, cmdOpts) => {
|
|
118
|
-
const opts = globalOpts();
|
|
119
|
-
const client = await createClient(opts);
|
|
120
|
-
const input = await parseInputData(cmdOpts);
|
|
121
|
-
const data = await client.request(UpdateHookDocument, { id, input });
|
|
122
|
-
formatOutput(data.updateHook, opts);
|
|
123
|
-
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
124
|
-
success(`Hook "${data.updateHook.key}" updated`);
|
|
125
|
-
}
|
|
126
|
-
}));
|
|
127
|
-
// delete
|
|
128
|
-
hooks
|
|
129
|
-
.command('delete <id>')
|
|
130
|
-
.description('Delete a hook')
|
|
131
|
-
.option('--confirm', 'Skip confirmation')
|
|
132
|
-
.action(withErrorHandler(globalOpts, async (id, cmdOpts) => {
|
|
133
|
-
const opts = globalOpts();
|
|
134
|
-
const confirmed = await confirmAction(`Delete hook ${id}?`, cmdOpts);
|
|
135
|
-
if (!confirmed)
|
|
136
|
-
return;
|
|
137
|
-
const client = await createClient(opts);
|
|
138
|
-
await client.request(DeleteHookDocument, { id });
|
|
139
|
-
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
140
|
-
success(`Hook ${id} deleted`);
|
|
141
|
-
}
|
|
142
|
-
}));
|
|
143
|
-
// deliveries
|
|
144
|
-
hooks
|
|
145
|
-
.command('deliveries')
|
|
146
|
-
.description('List hook deliveries')
|
|
147
|
-
.option('--hook-id <id>', 'Filter by hook ID')
|
|
148
|
-
.option('--event <event>', 'Filter by event')
|
|
149
|
-
.option('--status <status>', 'Filter by status (SUCCESS, FAILED, RETRYING)')
|
|
150
|
-
.option('--limit <n>', 'Max results', '20')
|
|
151
|
-
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
152
|
-
const opts = globalOpts();
|
|
153
|
-
const client = await createClient(opts);
|
|
154
|
-
const data = await client.request(HookDeliveriesDocument, {
|
|
155
|
-
hookId: cmdOpts.hookId,
|
|
156
|
-
event: cmdOpts.event,
|
|
157
|
-
status: cmdOpts.status,
|
|
158
|
-
limit: parseInt(String(cmdOpts.limit ?? '20'), 10),
|
|
159
|
-
});
|
|
160
|
-
formatList(data.hookDeliveries.items, opts, {
|
|
161
|
-
columns: [
|
|
162
|
-
{ key: 'id', header: 'ID', width: 28 },
|
|
163
|
-
{ key: 'event', header: 'Event', width: 22 },
|
|
164
|
-
{ key: 'status', header: 'Status', width: 10 },
|
|
165
|
-
{ key: 'attempts', header: 'Tries', width: 6 },
|
|
166
|
-
{
|
|
167
|
-
key: 'durationMs',
|
|
168
|
-
header: 'Duration',
|
|
169
|
-
width: 10,
|
|
170
|
-
format: (v) => (v ? `${v}ms` : '—'),
|
|
171
|
-
},
|
|
172
|
-
{ key: 'error', header: 'Error', width: 30 },
|
|
173
|
-
{
|
|
174
|
-
key: 'deliveredAt',
|
|
175
|
-
header: 'Delivered',
|
|
176
|
-
width: 12,
|
|
177
|
-
format: (v) => timeAgo(v),
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
total: data.hookDeliveries.total,
|
|
181
|
-
});
|
|
182
|
-
}));
|
|
183
|
-
// retry-delivery
|
|
184
|
-
hooks
|
|
185
|
-
.command('retry-delivery <deliveryId>')
|
|
186
|
-
.description('Retry a failed hook delivery')
|
|
187
|
-
.action(withErrorHandler(globalOpts, async (deliveryId) => {
|
|
188
|
-
const opts = globalOpts();
|
|
189
|
-
const client = await createClient(opts);
|
|
190
|
-
const data = await client.request(RetryHookDeliveryDocument, {
|
|
191
|
-
deliveryId,
|
|
192
|
-
});
|
|
193
|
-
formatOutput(data.retryHookDelivery, opts);
|
|
194
|
-
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
195
|
-
const status = data.retryHookDelivery.status;
|
|
196
|
-
if (status === 'SUCCESS')
|
|
197
|
-
success('Delivery retry succeeded');
|
|
198
|
-
else
|
|
199
|
-
console.error(`Delivery retry status: ${status}`);
|
|
200
|
-
}
|
|
201
|
-
}));
|
|
202
|
-
// test
|
|
203
|
-
hooks
|
|
204
|
-
.command('test <hookId>')
|
|
205
|
-
.description('Send a test event to a hook')
|
|
206
|
-
.option('-d, --data <json>', 'Test payload as JSON')
|
|
207
|
-
.option('-f, --file <path>', 'Read payload from file')
|
|
208
|
-
.action(withErrorHandler(globalOpts, async (hookId, cmdOpts) => {
|
|
209
|
-
const opts = globalOpts();
|
|
210
|
-
const client = await createClient(opts);
|
|
211
|
-
let testData;
|
|
212
|
-
if (cmdOpts.data || cmdOpts.file) {
|
|
213
|
-
testData = await parseInputData(cmdOpts);
|
|
214
|
-
}
|
|
215
|
-
const data = await client.request(TestHookDocument, {
|
|
216
|
-
hookId,
|
|
217
|
-
data: testData,
|
|
218
|
-
});
|
|
219
|
-
formatOutput(data.testHook, opts);
|
|
220
|
-
if (!(opts.json || opts.jsonl || opts.quiet)) {
|
|
221
|
-
const status = data.testHook.status;
|
|
222
|
-
if (status === 'SUCCESS')
|
|
223
|
-
success('Test delivery succeeded');
|
|
224
|
-
else
|
|
225
|
-
console.error(`Test delivery status: ${status}`);
|
|
226
|
-
}
|
|
227
|
-
}));
|
|
228
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../src/commands/locales.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAetD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CA8JN"}
|