@eide/foir-cli 0.1.17 → 0.1.19
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 +3 -0
- package/dist/codegen/fetch-models.d.ts +14 -0
- package/dist/codegen/fetch-models.d.ts.map +1 -1
- package/dist/codegen/fetch-models.js +42 -1
- package/dist/codegen/field-mapping.d.ts.map +1 -1
- package/dist/codegen/field-mapping.js +4 -10
- package/dist/codegen/generators/documents.d.ts.map +1 -1
- package/dist/codegen/generators/documents.js +4 -0
- package/dist/codegen/generators/model-types.js +3 -5
- package/dist/codegen/generators/static-documents.d.ts.map +1 -1
- package/dist/codegen/generators/static-documents.js +94 -56
- package/dist/codegen/swift-field-mapping.d.ts.map +1 -1
- package/dist/codegen/swift-field-mapping.js +0 -6
- package/dist/commands/create-extension.d.ts +4 -0
- package/dist/commands/create-extension.d.ts.map +1 -0
- package/dist/commands/create-extension.js +60 -0
- package/dist/scaffold/package-manager.d.ts +12 -0
- package/dist/scaffold/package-manager.d.ts.map +1 -0
- package/dist/scaffold/package-manager.js +51 -0
- package/dist/scaffold/scaffold.d.ts +4 -0
- package/dist/scaffold/scaffold.d.ts.map +1 -0
- package/dist/scaffold/scaffold.js +462 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ import { registerFilesCommands } from './commands/files.js';
|
|
|
36
36
|
import { registerNotesCommands } from './commands/notes.js';
|
|
37
37
|
import { registerVariantCatalogCommands } from './commands/variant-catalog.js';
|
|
38
38
|
import { registerPullCommand } from './commands/pull.js';
|
|
39
|
+
import { registerCreateExtensionCommand } from './commands/create-extension.js';
|
|
39
40
|
const program = new Command();
|
|
40
41
|
program
|
|
41
42
|
.name('foir')
|
|
@@ -81,4 +82,6 @@ registerNotesCommands(program, getGlobalOpts);
|
|
|
81
82
|
registerVariantCatalogCommands(program, getGlobalOpts);
|
|
82
83
|
// Codegen
|
|
83
84
|
registerPullCommand(program, getGlobalOpts);
|
|
85
|
+
// Scaffolding
|
|
86
|
+
registerCreateExtensionCommand(program, getGlobalOpts);
|
|
84
87
|
program.parse();
|
|
@@ -33,6 +33,20 @@ export interface CodegenModel {
|
|
|
33
33
|
config: CodegenModelConfig;
|
|
34
34
|
hooks?: Record<string, unknown>;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalize a field's item type reference into `options.itemType`.
|
|
38
|
+
*
|
|
39
|
+
* The platform stores the reference in different locations depending on
|
|
40
|
+
* how the field was created:
|
|
41
|
+
* - `options.itemType` (standard)
|
|
42
|
+
* - `config.itemType` (config variant)
|
|
43
|
+
* - `config.itemSchema` (inline model schema reference)
|
|
44
|
+
* - top-level `itemType` (inline model field definitions)
|
|
45
|
+
*
|
|
46
|
+
* We consolidate into `options.itemType` so downstream codegen has a
|
|
47
|
+
* single location to check.
|
|
48
|
+
*/
|
|
49
|
+
export declare function normalizeField(raw: Record<string, unknown>): FieldSchemaForGen;
|
|
36
50
|
export declare function fetchModelsForCodegen(client: GraphQLClient): Promise<CodegenModel[]>;
|
|
37
51
|
/**
|
|
38
52
|
* Filter models based on pull config options.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-models.d.ts","sourceRoot":"","sources":["../../src/codegen/fetch-models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,iBAAiB,EAAE,OAAO,CAAC;KAC5B,CAAC;IACF,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACvD,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAsCD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,YAAY,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"fetch-models.d.ts","sourceRoot":"","sources":["../../src/codegen/fetch-models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,iBAAiB,EAAE,OAAO,CAAC;KAC5B,CAAC;IACF,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACvD,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAsCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAmC9E;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,YAAY,EAAE,CAAC,CAiBzB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACpD,YAAY,EAAE,CAahB"}
|
|
@@ -20,6 +20,47 @@ function normalizeConfig(raw) {
|
|
|
20
20
|
embeddings: raw.embeddings,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a field's item type reference into `options.itemType`.
|
|
25
|
+
*
|
|
26
|
+
* The platform stores the reference in different locations depending on
|
|
27
|
+
* how the field was created:
|
|
28
|
+
* - `options.itemType` (standard)
|
|
29
|
+
* - `config.itemType` (config variant)
|
|
30
|
+
* - `config.itemSchema` (inline model schema reference)
|
|
31
|
+
* - top-level `itemType` (inline model field definitions)
|
|
32
|
+
*
|
|
33
|
+
* We consolidate into `options.itemType` so downstream codegen has a
|
|
34
|
+
* single location to check.
|
|
35
|
+
*/
|
|
36
|
+
export function normalizeField(raw) {
|
|
37
|
+
const field = raw;
|
|
38
|
+
const options = { ...(field.options ?? {}) };
|
|
39
|
+
// Resolve itemType from all possible locations (first wins)
|
|
40
|
+
if (!options.itemType) {
|
|
41
|
+
const resolved = field.itemType ??
|
|
42
|
+
field.config?.itemType ??
|
|
43
|
+
field.config?.itemSchema;
|
|
44
|
+
if (resolved) {
|
|
45
|
+
options.itemType = resolved;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Carry over minItems/maxItems from config
|
|
49
|
+
if (field.config?.minItems !== undefined && options.minItems === undefined) {
|
|
50
|
+
options.minItems = field.config.minItems;
|
|
51
|
+
}
|
|
52
|
+
if (field.config?.maxItems !== undefined && options.maxItems === undefined) {
|
|
53
|
+
options.maxItems = field.config.maxItems;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
key: field.key,
|
|
57
|
+
type: field.type,
|
|
58
|
+
label: field.label,
|
|
59
|
+
required: field.required,
|
|
60
|
+
helpText: field.helpText,
|
|
61
|
+
options: Object.keys(options).length > 0 ? options : undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
23
64
|
export async function fetchModelsForCodegen(client) {
|
|
24
65
|
const data = await client.request(MODELS_FOR_CODEGEN, {
|
|
25
66
|
limit: 500,
|
|
@@ -30,7 +71,7 @@ export async function fetchModelsForCodegen(client) {
|
|
|
30
71
|
pluralName: item.pluralName,
|
|
31
72
|
description: item.description,
|
|
32
73
|
category: item.category,
|
|
33
|
-
fields: item.fields ?? [],
|
|
74
|
+
fields: (item.fields ?? []).map((f) => normalizeField(f)),
|
|
34
75
|
config: normalizeConfig(item.config ?? {}),
|
|
35
76
|
hooks: item.hooks,
|
|
36
77
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/field-mapping.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/field-mapping.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,aAqBhC,CAAC;AAEH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAyD1D,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GAClC,MAAM,CAqCR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAY3E;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EAAE,GAC1B,GAAG,CAAC,MAAM,CAAC,CAcb;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA8EjE"}
|
|
@@ -19,7 +19,6 @@ export const PRIMITIVE_FIELD_TYPES = new Set([
|
|
|
19
19
|
'multiselect',
|
|
20
20
|
'json',
|
|
21
21
|
'list',
|
|
22
|
-
'tree',
|
|
23
22
|
'entity-reference',
|
|
24
23
|
'reference',
|
|
25
24
|
'link',
|
|
@@ -65,7 +64,6 @@ export const FIELD_TYPE_MAPPING = {
|
|
|
65
64
|
multiselect: { outputType: 'string[]', inputType: 'string[]' },
|
|
66
65
|
json: { outputType: 'unknown', inputType: 'unknown' },
|
|
67
66
|
list: { outputType: 'unknown[]', inputType: 'unknown[]' },
|
|
68
|
-
tree: { outputType: 'unknown[]', inputType: 'unknown[]' },
|
|
69
67
|
flexible: {
|
|
70
68
|
outputType: 'FlexibleFieldItem[]',
|
|
71
69
|
inputType: 'FlexibleFieldItem[]',
|
|
@@ -109,8 +107,7 @@ export function getFieldType(field, mode = 'output') {
|
|
|
109
107
|
tsType = `(${options.map((o) => `'${o.value}'`).join(' | ')})[]`;
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
|
-
if (
|
|
113
|
-
field.options?.itemType) {
|
|
110
|
+
if (field.type === 'list' && field.options?.itemType) {
|
|
114
111
|
const itemType = field.options.itemType;
|
|
115
112
|
const itemMapping = FIELD_TYPE_MAPPING[itemType];
|
|
116
113
|
if (itemMapping) {
|
|
@@ -128,8 +125,7 @@ export function getRequiredImports(fields) {
|
|
|
128
125
|
const mapping = FIELD_TYPE_MAPPING[field.type];
|
|
129
126
|
if (mapping?.needsImport)
|
|
130
127
|
imports.add(mapping.needsImport);
|
|
131
|
-
if (
|
|
132
|
-
field.options?.itemType) {
|
|
128
|
+
if (field.type === 'list' && field.options?.itemType) {
|
|
133
129
|
const itemMapping = FIELD_TYPE_MAPPING[field.options.itemType];
|
|
134
130
|
if (itemMapping?.needsImport)
|
|
135
131
|
imports.add(itemMapping.needsImport);
|
|
@@ -143,8 +139,7 @@ export function getInlineSchemaReferences(fields) {
|
|
|
143
139
|
if (!isPrimitiveFieldType(field.type) && !FIELD_TYPE_MAPPING[field.type]) {
|
|
144
140
|
refs.add(field.type);
|
|
145
141
|
}
|
|
146
|
-
if (
|
|
147
|
-
field.options?.itemType) {
|
|
142
|
+
if (field.type === 'list' && field.options?.itemType) {
|
|
148
143
|
const itemType = field.options.itemType;
|
|
149
144
|
if (!isPrimitiveFieldType(itemType) && !FIELD_TYPE_MAPPING[itemType]) {
|
|
150
145
|
refs.add(itemType);
|
|
@@ -223,8 +218,7 @@ export function generateFieldDef(field) {
|
|
|
223
218
|
if (field.options.multiple)
|
|
224
219
|
parts.push('multiple: true');
|
|
225
220
|
}
|
|
226
|
-
if (
|
|
227
|
-
field.options?.itemType) {
|
|
221
|
+
if (field.type === 'list' && field.options?.itemType) {
|
|
228
222
|
parts.push(`itemType: '${field.options.itemType}'`);
|
|
229
223
|
if (field.options.minItems !== undefined)
|
|
230
224
|
parts.push(`minItems: ${field.options.minItems}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/documents.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAiGlE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/documents.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAiGlE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAoBhD"}
|
|
@@ -111,12 +111,16 @@ export function generateSharedFragments() {
|
|
|
111
111
|
fragment ShareFields on Share {
|
|
112
112
|
id
|
|
113
113
|
resourceType
|
|
114
|
+
recordId
|
|
115
|
+
fileId
|
|
114
116
|
permission
|
|
115
117
|
status
|
|
118
|
+
sharedWithCustomerId
|
|
116
119
|
acceptedAt
|
|
117
120
|
declinedAt
|
|
118
121
|
expiresAt
|
|
119
122
|
createdAt
|
|
123
|
+
createdBy
|
|
120
124
|
revokedAt
|
|
121
125
|
}
|
|
122
126
|
`;
|
|
@@ -118,9 +118,8 @@ function generateDataInterface(model, fields, interfaceName, allModels) {
|
|
|
118
118
|
if (refModel && !isInlineOnlyModel(refModel)) {
|
|
119
119
|
fieldType = toPascalCase(field.type) + 'Data';
|
|
120
120
|
}
|
|
121
|
-
// Handle list
|
|
122
|
-
if (
|
|
123
|
-
field.options?.itemType) {
|
|
121
|
+
// Handle list itemType refs
|
|
122
|
+
if (field.type === 'list' && field.options?.itemType) {
|
|
124
123
|
const itemRefModel = allModels.find((m) => m.key === field.options.itemType);
|
|
125
124
|
if (itemRefModel && !isInlineOnlyModel(itemRefModel)) {
|
|
126
125
|
fieldType = toPascalCase(field.options.itemType) + 'Data[]';
|
|
@@ -140,8 +139,7 @@ function getFieldTypeImportsForFields(fields) {
|
|
|
140
139
|
if (mapping?.needsImport === 'field-types') {
|
|
141
140
|
imports.add(mapping.outputType);
|
|
142
141
|
}
|
|
143
|
-
if (
|
|
144
|
-
field.options?.itemType) {
|
|
142
|
+
if (field.type === 'list' && field.options?.itemType) {
|
|
145
143
|
const itemMapping = FIELD_TYPE_MAPPING[field.options.itemType];
|
|
146
144
|
if (itemMapping?.needsImport === 'field-types') {
|
|
147
145
|
imports.add(itemMapping.outputType);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-documents.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/static-documents.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"static-documents.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/static-documents.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AA4uB1D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC9B,kBAAkB,EAAE,CAetB"}
|
|
@@ -9,39 +9,44 @@ ${HEADER}
|
|
|
9
9
|
|
|
10
10
|
mutation CustomerLogin($email: String!, $password: String!) {
|
|
11
11
|
customerLogin(email: $email, password: $password) {
|
|
12
|
-
|
|
12
|
+
success
|
|
13
|
+
accessToken
|
|
13
14
|
refreshToken
|
|
14
|
-
|
|
15
|
+
user { id email status }
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
mutation CustomerRegister($email: String!, $password: String!) {
|
|
19
20
|
customerRegister(email: $email, password: $password) {
|
|
20
|
-
|
|
21
|
+
success
|
|
22
|
+
accessToken
|
|
21
23
|
refreshToken
|
|
22
|
-
|
|
24
|
+
user { id email status }
|
|
25
|
+
emailVerificationRequired
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
mutation CustomerRequestOTP($email: String!) {
|
|
27
30
|
customerRequestOTP(email: $email) {
|
|
28
31
|
success
|
|
32
|
+
expiresAt
|
|
29
33
|
message
|
|
30
|
-
expiresIn
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
mutation CustomerLoginOTP($email: String!, $otp: String!) {
|
|
35
38
|
customerLoginOTP(email: $email, otp: $otp) {
|
|
36
|
-
|
|
39
|
+
success
|
|
40
|
+
accessToken
|
|
37
41
|
refreshToken
|
|
38
|
-
|
|
42
|
+
user { id email status }
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
mutation CustomerRefreshToken($refreshToken: String!) {
|
|
43
47
|
customerRefreshToken(refreshToken: $refreshToken) {
|
|
44
|
-
|
|
48
|
+
success
|
|
49
|
+
accessToken
|
|
45
50
|
refreshToken
|
|
46
51
|
}
|
|
47
52
|
}
|
|
@@ -70,7 +75,8 @@ mutation CustomerUpdatePassword($currentPassword: String!, $newPassword: String!
|
|
|
70
75
|
mutation CustomerVerifyEmail($token: String!) {
|
|
71
76
|
customerVerifyEmail(token: $token) {
|
|
72
77
|
success
|
|
73
|
-
|
|
78
|
+
user { id email }
|
|
79
|
+
message
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -90,15 +96,16 @@ mutation CustomerLogout {
|
|
|
90
96
|
|
|
91
97
|
query AuthConfig($tenantId: ID) {
|
|
92
98
|
authConfig(tenantId: $tenantId) {
|
|
93
|
-
|
|
99
|
+
authMethods
|
|
94
100
|
passwordPolicy {
|
|
95
101
|
minLength
|
|
96
102
|
requireUppercase
|
|
97
103
|
requireLowercase
|
|
98
104
|
requireNumbers
|
|
99
105
|
requireSpecialChars
|
|
106
|
+
requireSpecial
|
|
100
107
|
}
|
|
101
|
-
|
|
108
|
+
publicRegistrationEnabled
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
|
|
@@ -106,8 +113,9 @@ query CurrentUser {
|
|
|
106
113
|
currentUser {
|
|
107
114
|
id
|
|
108
115
|
email
|
|
116
|
+
emailVerified
|
|
109
117
|
status
|
|
110
|
-
|
|
118
|
+
userType
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
`;
|
|
@@ -122,7 +130,9 @@ query AuthProviders {
|
|
|
122
130
|
key
|
|
123
131
|
name
|
|
124
132
|
type
|
|
133
|
+
enabled
|
|
125
134
|
isDefault
|
|
135
|
+
priority
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
|
|
@@ -132,33 +142,47 @@ query DefaultAuthProvider {
|
|
|
132
142
|
key
|
|
133
143
|
name
|
|
134
144
|
type
|
|
145
|
+
enabled
|
|
135
146
|
isDefault
|
|
147
|
+
priority
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
mutation CustomerLoginWithProvider($input: ProviderLoginInput!) {
|
|
140
152
|
customerLoginWithProvider(input: $input) {
|
|
153
|
+
method
|
|
154
|
+
providerId
|
|
155
|
+
providerKey
|
|
141
156
|
redirectUrl
|
|
142
|
-
|
|
143
|
-
|
|
157
|
+
accessToken
|
|
158
|
+
refreshToken
|
|
159
|
+
user { id email userType }
|
|
160
|
+
otpSent
|
|
161
|
+
email
|
|
162
|
+
expiresAt
|
|
163
|
+
state
|
|
144
164
|
}
|
|
145
165
|
}
|
|
146
166
|
|
|
147
167
|
mutation CustomerProviderCallback($input: ProviderCallbackInput!) {
|
|
148
168
|
customerProviderCallback(input: $input) {
|
|
149
|
-
|
|
169
|
+
accessToken
|
|
150
170
|
refreshToken
|
|
151
|
-
|
|
171
|
+
user { id email userType }
|
|
152
172
|
isNewCustomer
|
|
173
|
+
providerAccessToken
|
|
174
|
+
providerAccessTokenExpiresIn
|
|
153
175
|
}
|
|
154
176
|
}
|
|
155
177
|
|
|
156
178
|
mutation CustomerProviderVerifyOTP($input: ProviderOTPVerifyInput!) {
|
|
157
179
|
customerProviderVerifyOTP(input: $input) {
|
|
158
|
-
|
|
180
|
+
accessToken
|
|
159
181
|
refreshToken
|
|
160
|
-
|
|
182
|
+
user { id email userType }
|
|
161
183
|
isNewCustomer
|
|
184
|
+
providerAccessToken
|
|
185
|
+
providerAccessTokenExpiresIn
|
|
162
186
|
}
|
|
163
187
|
}
|
|
164
188
|
`;
|
|
@@ -173,15 +197,22 @@ query GetFile($id: ID!) {
|
|
|
173
197
|
filename
|
|
174
198
|
mimeType
|
|
175
199
|
size
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
storageKey
|
|
200
|
+
url
|
|
201
|
+
source
|
|
179
202
|
status
|
|
180
|
-
tags
|
|
181
203
|
metadata
|
|
204
|
+
width
|
|
205
|
+
height
|
|
206
|
+
blurhash
|
|
207
|
+
dominantColor
|
|
208
|
+
duration
|
|
209
|
+
thumbnailUrl
|
|
210
|
+
previewUrl
|
|
182
211
|
altText
|
|
183
212
|
caption
|
|
184
213
|
description
|
|
214
|
+
isImage
|
|
215
|
+
isVideo
|
|
185
216
|
createdAt
|
|
186
217
|
updatedAt
|
|
187
218
|
}
|
|
@@ -203,7 +234,6 @@ mutation CreateFileUpload(
|
|
|
203
234
|
) {
|
|
204
235
|
uploadId
|
|
205
236
|
uploadUrl
|
|
206
|
-
fileId
|
|
207
237
|
expiresAt
|
|
208
238
|
}
|
|
209
239
|
}
|
|
@@ -214,7 +244,8 @@ mutation ConfirmFileUpload($uploadId: ID!) {
|
|
|
214
244
|
filename
|
|
215
245
|
mimeType
|
|
216
246
|
size
|
|
217
|
-
|
|
247
|
+
url
|
|
248
|
+
source
|
|
218
249
|
status
|
|
219
250
|
createdAt
|
|
220
251
|
}
|
|
@@ -289,13 +320,15 @@ query CustomerNotifications(
|
|
|
289
320
|
items {
|
|
290
321
|
id
|
|
291
322
|
type
|
|
292
|
-
title
|
|
293
|
-
body
|
|
294
323
|
category
|
|
295
|
-
|
|
296
|
-
|
|
324
|
+
title
|
|
325
|
+
message
|
|
297
326
|
actionUrl
|
|
327
|
+
imageUrl
|
|
298
328
|
metadata
|
|
329
|
+
alertChannels
|
|
330
|
+
isRead
|
|
331
|
+
readAt
|
|
299
332
|
createdAt
|
|
300
333
|
}
|
|
301
334
|
total
|
|
@@ -310,10 +343,10 @@ query CustomerUnreadCount($category: String) {
|
|
|
310
343
|
|
|
311
344
|
query NotificationPreferences {
|
|
312
345
|
notificationPreferences {
|
|
346
|
+
id
|
|
313
347
|
category
|
|
314
348
|
channel
|
|
315
349
|
enabled
|
|
316
|
-
updatedAt
|
|
317
350
|
}
|
|
318
351
|
}
|
|
319
352
|
|
|
@@ -321,9 +354,15 @@ mutation SendNotification($input: SendNotificationInput!) {
|
|
|
321
354
|
sendNotification(input: $input) {
|
|
322
355
|
id
|
|
323
356
|
type
|
|
324
|
-
title
|
|
325
|
-
body
|
|
326
357
|
category
|
|
358
|
+
title
|
|
359
|
+
message
|
|
360
|
+
actionUrl
|
|
361
|
+
imageUrl
|
|
362
|
+
metadata
|
|
363
|
+
alertChannels
|
|
364
|
+
isRead
|
|
365
|
+
readAt
|
|
327
366
|
createdAt
|
|
328
367
|
}
|
|
329
368
|
}
|
|
@@ -332,7 +371,6 @@ mutation SendBulkNotifications($input: SendBulkNotificationsInput!) {
|
|
|
332
371
|
sendBulkNotifications(input: $input) {
|
|
333
372
|
sent
|
|
334
373
|
failed
|
|
335
|
-
jobId
|
|
336
374
|
}
|
|
337
375
|
}
|
|
338
376
|
|
|
@@ -351,8 +389,10 @@ mutation MarkAllCustomerNotificationsRead($category: String) {
|
|
|
351
389
|
mutation RegisterDeviceToken($input: RegisterDeviceTokenInput!) {
|
|
352
390
|
registerDeviceToken(input: $input) {
|
|
353
391
|
id
|
|
354
|
-
token
|
|
355
392
|
platform
|
|
393
|
+
token
|
|
394
|
+
deviceName
|
|
395
|
+
isActive
|
|
356
396
|
createdAt
|
|
357
397
|
}
|
|
358
398
|
}
|
|
@@ -363,10 +403,10 @@ mutation UnregisterDeviceToken($token: String!) {
|
|
|
363
403
|
|
|
364
404
|
mutation UpdateNotificationPreference($input: UpdateNotificationPreferenceInput!) {
|
|
365
405
|
updateNotificationPreference(input: $input) {
|
|
406
|
+
id
|
|
366
407
|
category
|
|
367
408
|
channel
|
|
368
409
|
enabled
|
|
369
|
-
updatedAt
|
|
370
410
|
}
|
|
371
411
|
}
|
|
372
412
|
`;
|
|
@@ -381,14 +421,11 @@ query GetOperationExecution($id: ID!) {
|
|
|
381
421
|
operationKey
|
|
382
422
|
status
|
|
383
423
|
result
|
|
384
|
-
error
|
|
385
|
-
code
|
|
386
|
-
message
|
|
387
|
-
}
|
|
388
|
-
executionId
|
|
389
|
-
durationMs
|
|
424
|
+
error
|
|
390
425
|
startedAt
|
|
391
426
|
completedAt
|
|
427
|
+
durationMs
|
|
428
|
+
metadata
|
|
392
429
|
createdAt
|
|
393
430
|
}
|
|
394
431
|
}
|
|
@@ -412,6 +449,7 @@ query ListOperationExecutions(
|
|
|
412
449
|
durationMs
|
|
413
450
|
startedAt
|
|
414
451
|
completedAt
|
|
452
|
+
metadata
|
|
415
453
|
createdAt
|
|
416
454
|
}
|
|
417
455
|
total
|
|
@@ -428,6 +466,7 @@ mutation ExecuteOperation($input: ExecuteOperationInput!) {
|
|
|
428
466
|
}
|
|
429
467
|
executionId
|
|
430
468
|
durationMs
|
|
469
|
+
metadata
|
|
431
470
|
}
|
|
432
471
|
}
|
|
433
472
|
|
|
@@ -453,16 +492,12 @@ query GetSchedule($key: String!) {
|
|
|
453
492
|
cronDescription
|
|
454
493
|
timezone
|
|
455
494
|
targetType
|
|
456
|
-
targetConfig
|
|
457
495
|
isActive
|
|
458
|
-
pausedAt
|
|
459
496
|
lastRunAt
|
|
460
497
|
lastRunStatus
|
|
461
|
-
lastRunError
|
|
462
498
|
nextRunAt
|
|
463
499
|
runCount
|
|
464
500
|
failureCount
|
|
465
|
-
consecutiveFailures
|
|
466
501
|
createdAt
|
|
467
502
|
updatedAt
|
|
468
503
|
}
|
|
@@ -530,7 +565,6 @@ mutation PauseSchedule($key: String!) {
|
|
|
530
565
|
id
|
|
531
566
|
key
|
|
532
567
|
isActive
|
|
533
|
-
pausedAt
|
|
534
568
|
}
|
|
535
569
|
}
|
|
536
570
|
|
|
@@ -558,15 +592,17 @@ ${HEADER}
|
|
|
558
592
|
fragment ShareFields on Share {
|
|
559
593
|
id
|
|
560
594
|
resourceType
|
|
561
|
-
|
|
595
|
+
recordId
|
|
596
|
+
fileId
|
|
562
597
|
permission
|
|
563
598
|
status
|
|
564
599
|
sharedWithCustomerId
|
|
565
600
|
acceptedAt
|
|
566
601
|
declinedAt
|
|
567
602
|
expiresAt
|
|
568
|
-
revokedAt
|
|
569
603
|
createdAt
|
|
604
|
+
createdBy
|
|
605
|
+
revokedAt
|
|
570
606
|
}
|
|
571
607
|
|
|
572
608
|
query GetShares($resourceType: ShareResourceType!, $resourceId: ID!, $status: ShareStatus) {
|
|
@@ -656,13 +692,11 @@ ${HEADER}
|
|
|
656
692
|
|
|
657
693
|
query SemanticSearch($input: SemanticSearchInput!) {
|
|
658
694
|
semanticSearch(input: $input) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
score
|
|
695
|
+
recordId
|
|
696
|
+
source
|
|
697
|
+
modelKey
|
|
698
|
+
naturalKey
|
|
699
|
+
similarity
|
|
666
700
|
highlights
|
|
667
701
|
}
|
|
668
702
|
}
|
|
@@ -670,9 +704,10 @@ query SemanticSearch($input: SemanticSearchInput!) {
|
|
|
670
704
|
query EmbeddingStatus($recordIds: [ID!]!, $source: EmbeddingSource!) {
|
|
671
705
|
embeddingStatus(recordIds: $recordIds, source: $source) {
|
|
672
706
|
recordId
|
|
673
|
-
|
|
707
|
+
hasEmbedding
|
|
708
|
+
lastUpdated
|
|
709
|
+
model
|
|
674
710
|
dimensions
|
|
675
|
-
updatedAt
|
|
676
711
|
}
|
|
677
712
|
}
|
|
678
713
|
|
|
@@ -683,6 +718,9 @@ query IsAIEnabled {
|
|
|
683
718
|
mutation GenerateEmbedding($recordId: ID!, $modelKey: String!, $source: EmbeddingSource!) {
|
|
684
719
|
generateEmbedding(recordId: $recordId, modelKey: $modelKey, source: $source) {
|
|
685
720
|
success
|
|
721
|
+
embeddingId
|
|
722
|
+
recordId
|
|
723
|
+
tokenCount
|
|
686
724
|
dimensions
|
|
687
725
|
}
|
|
688
726
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swift-field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/swift-field-mapping.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"swift-field-mapping.d.ts","sourceRoot":"","sources":["../../src/codegen/swift-field-mapping.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CA8HrE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC,CAeA"}
|
|
@@ -105,12 +105,6 @@ export const SWIFT_FIELD_TYPE_MAPPING = {
|
|
|
105
105
|
defaultValue: '[]',
|
|
106
106
|
castExpression: 'as? [Any]',
|
|
107
107
|
},
|
|
108
|
-
tree: {
|
|
109
|
-
type: '[Any]',
|
|
110
|
-
alwaysOptional: true,
|
|
111
|
-
defaultValue: '[]',
|
|
112
|
-
castExpression: 'as? [Any]',
|
|
113
|
-
},
|
|
114
108
|
flexible: {
|
|
115
109
|
type: '[[String: Any]]',
|
|
116
110
|
alwaysOptional: true,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-extension.d.ts","sourceRoot":"","sources":["../../src/commands/create-extension.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAgBtD,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CA8DN"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { withErrorHandler } from '../lib/errors.js';
|
|
4
|
+
import { scaffold } from '../scaffold/scaffold.js';
|
|
5
|
+
const EXTENSION_TYPES = [
|
|
6
|
+
'custom-editor',
|
|
7
|
+
'workflow',
|
|
8
|
+
'widget',
|
|
9
|
+
];
|
|
10
|
+
function isValidExtensionType(value) {
|
|
11
|
+
return EXTENSION_TYPES.includes(value);
|
|
12
|
+
}
|
|
13
|
+
export function registerCreateExtensionCommand(program, globalOpts) {
|
|
14
|
+
program
|
|
15
|
+
.command('create-extension [name]')
|
|
16
|
+
.description('Scaffold a new Foir extension')
|
|
17
|
+
.option('--type <type>', 'Extension type: custom-editor, workflow, widget')
|
|
18
|
+
.option('--api-url <url>', 'Platform API URL', 'http://localhost:4000/graphql')
|
|
19
|
+
.action(withErrorHandler(globalOpts, async (name, cmdOpts) => {
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(chalk.bold(' Create Foir Extension'));
|
|
22
|
+
console.log(chalk.gray(' ---------------------'));
|
|
23
|
+
console.log();
|
|
24
|
+
// Resolve name — use arg or prompt
|
|
25
|
+
let extensionName = name;
|
|
26
|
+
if (!extensionName) {
|
|
27
|
+
const { inputName } = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: 'input',
|
|
30
|
+
name: 'inputName',
|
|
31
|
+
message: 'Extension name:',
|
|
32
|
+
default: 'my-extension',
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
extensionName = inputName;
|
|
36
|
+
}
|
|
37
|
+
// Resolve type — use flag or prompt
|
|
38
|
+
let extensionType;
|
|
39
|
+
if (cmdOpts?.type && isValidExtensionType(cmdOpts.type)) {
|
|
40
|
+
extensionType = cmdOpts.type;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const { selectedType } = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'list',
|
|
46
|
+
name: 'selectedType',
|
|
47
|
+
message: 'Extension type:',
|
|
48
|
+
choices: EXTENSION_TYPES,
|
|
49
|
+
default: 'custom-editor',
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
extensionType = selectedType;
|
|
53
|
+
}
|
|
54
|
+
const apiUrl = cmdOpts?.apiUrl ?? 'http://localhost:4000/graphql';
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(` Scaffolding ${chalk.cyan(`"${extensionName}"`)} (${extensionType})...`);
|
|
57
|
+
console.log();
|
|
58
|
+
await scaffold(extensionName, extensionType, apiUrl);
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type PackageManager = 'pnpm' | 'yarn' | 'npm';
|
|
2
|
+
export interface PackageManagerInfo {
|
|
3
|
+
name: PackageManager;
|
|
4
|
+
installCommand: string;
|
|
5
|
+
execCommand: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Detects the package manager by checking for lock files in the
|
|
9
|
+
* current working directory and parent directories.
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectPackageManager(): PackageManagerInfo;
|
|
12
|
+
//# sourceMappingURL=package-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../src/scaffold/package-manager.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,kBAAkB,CA0BzD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Detects the package manager by checking for lock files in the
|
|
5
|
+
* current working directory and parent directories.
|
|
6
|
+
*/
|
|
7
|
+
export function detectPackageManager() {
|
|
8
|
+
const lockFiles = [
|
|
9
|
+
{ file: 'pnpm-lock.yaml', manager: 'pnpm' },
|
|
10
|
+
{ file: 'yarn.lock', manager: 'yarn' },
|
|
11
|
+
{ file: 'package-lock.json', manager: 'npm' },
|
|
12
|
+
];
|
|
13
|
+
let dir = process.cwd();
|
|
14
|
+
// Walk up the directory tree looking for lock files
|
|
15
|
+
while (true) {
|
|
16
|
+
for (const { file, manager } of lockFiles) {
|
|
17
|
+
if (fs.existsSync(path.join(dir, file))) {
|
|
18
|
+
return getManagerInfo(manager);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const parentDir = path.dirname(dir);
|
|
22
|
+
if (parentDir === dir) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
dir = parentDir;
|
|
26
|
+
}
|
|
27
|
+
// Default to npm if no lock file found
|
|
28
|
+
return getManagerInfo('npm');
|
|
29
|
+
}
|
|
30
|
+
function getManagerInfo(manager) {
|
|
31
|
+
switch (manager) {
|
|
32
|
+
case 'pnpm':
|
|
33
|
+
return {
|
|
34
|
+
name: 'pnpm',
|
|
35
|
+
installCommand: 'pnpm install',
|
|
36
|
+
execCommand: 'pnpm dlx',
|
|
37
|
+
};
|
|
38
|
+
case 'yarn':
|
|
39
|
+
return {
|
|
40
|
+
name: 'yarn',
|
|
41
|
+
installCommand: 'yarn install',
|
|
42
|
+
execCommand: 'yarn dlx',
|
|
43
|
+
};
|
|
44
|
+
case 'npm':
|
|
45
|
+
return {
|
|
46
|
+
name: 'npm',
|
|
47
|
+
installCommand: 'npm install',
|
|
48
|
+
execCommand: 'npx',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/scaffold/scaffold.ts"],"names":[],"mappings":"AAIA,KAAK,aAAa,GAAG,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE7D,wBAAsB,QAAQ,CAC5B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CA0Cf"}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { detectPackageManager } from './package-manager.js';
|
|
4
|
+
export async function scaffold(projectName, extensionType, apiUrl) {
|
|
5
|
+
const projectDir = path.resolve(process.cwd(), projectName);
|
|
6
|
+
if (fs.existsSync(projectDir)) {
|
|
7
|
+
throw new Error(`Directory "${projectName}" already exists. Choose a different name or remove the existing directory.`);
|
|
8
|
+
}
|
|
9
|
+
// Create directory structure
|
|
10
|
+
const files = getFiles(projectName, extensionType, apiUrl);
|
|
11
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
12
|
+
const fullPath = path.join(projectDir, filePath);
|
|
13
|
+
const dir = path.dirname(fullPath);
|
|
14
|
+
if (!fs.existsSync(dir)) {
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
// Detect package manager and print instructions
|
|
20
|
+
const pm = detectPackageManager();
|
|
21
|
+
console.log(' Files created:');
|
|
22
|
+
console.log();
|
|
23
|
+
for (const filePath of Object.keys(files)) {
|
|
24
|
+
console.log(` ${filePath}`);
|
|
25
|
+
}
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(' Done! Next steps:');
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(` cd ${projectName}`);
|
|
30
|
+
console.log(` ${pm.installCommand}`);
|
|
31
|
+
console.log(` cp ui/.env.example ui/.env.local`);
|
|
32
|
+
console.log(` cp api/.env.example api/.env.local`);
|
|
33
|
+
console.log(` ${pm.name === 'npm' ? 'npm run' : pm.name} dev`);
|
|
34
|
+
console.log();
|
|
35
|
+
}
|
|
36
|
+
function getFiles(projectName, extensionType, apiUrl) {
|
|
37
|
+
return {
|
|
38
|
+
// Root
|
|
39
|
+
'package.json': getRootPackageJson(projectName),
|
|
40
|
+
'extension.manifest.json': getManifest(projectName, extensionType),
|
|
41
|
+
// UI (Vite SPA)
|
|
42
|
+
'ui/package.json': getUiPackageJson(projectName),
|
|
43
|
+
'ui/tsconfig.json': getUiTsconfig(),
|
|
44
|
+
'ui/vite.config.ts': getUiViteConfig(),
|
|
45
|
+
'ui/index.html': getUiIndexHtml(projectName),
|
|
46
|
+
'ui/.env.example': getUiEnvExample(apiUrl),
|
|
47
|
+
'ui/.gitignore': getUiGitignore(),
|
|
48
|
+
'ui/src/main.tsx': getUiMain(),
|
|
49
|
+
'ui/src/App.tsx': getUiApp(extensionType),
|
|
50
|
+
'ui/src/index.css': getUiCss(),
|
|
51
|
+
'ui/src/vite-env.d.ts': '/// <reference types="vite/client" />\n',
|
|
52
|
+
// API (Hono)
|
|
53
|
+
'api/package.json': getApiPackageJson(projectName),
|
|
54
|
+
'api/tsconfig.json': getApiTsconfig(),
|
|
55
|
+
'api/.env.example': getApiEnvExample(apiUrl),
|
|
56
|
+
'api/.gitignore': 'node_modules\ndist\n.env\n.env.local\n',
|
|
57
|
+
'api/src/index.ts': getApiIndex(),
|
|
58
|
+
'api/src/routes/webhooks.ts': getApiWebhooks(),
|
|
59
|
+
'api/src/routes/health.ts': getApiHealth(),
|
|
60
|
+
'api/src/lib/platform.ts': getApiPlatform(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Root
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
function getRootPackageJson(name) {
|
|
67
|
+
const pkg = {
|
|
68
|
+
name,
|
|
69
|
+
version: '0.1.0',
|
|
70
|
+
private: true,
|
|
71
|
+
scripts: {
|
|
72
|
+
dev: 'concurrently "pnpm --filter ./ui dev" "pnpm --filter ./api dev"',
|
|
73
|
+
build: 'pnpm --filter ./ui build && pnpm --filter ./api build',
|
|
74
|
+
},
|
|
75
|
+
devDependencies: {
|
|
76
|
+
concurrently: '^9.0.0',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
80
|
+
}
|
|
81
|
+
function getManifest(name, extensionType) {
|
|
82
|
+
const manifest = {
|
|
83
|
+
name: name.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
84
|
+
version: '0.1.0',
|
|
85
|
+
type: extensionType,
|
|
86
|
+
description: `${extensionType} extension`,
|
|
87
|
+
entityTypes: [],
|
|
88
|
+
};
|
|
89
|
+
return JSON.stringify(manifest, null, 2) + '\n';
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// UI (Vite SPA)
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
function getUiPackageJson(name) {
|
|
95
|
+
const pkg = {
|
|
96
|
+
name: `${name}-ui`,
|
|
97
|
+
version: '0.1.0',
|
|
98
|
+
private: true,
|
|
99
|
+
type: 'module',
|
|
100
|
+
scripts: {
|
|
101
|
+
dev: 'vite',
|
|
102
|
+
build: 'tsc && vite build',
|
|
103
|
+
preview: 'vite preview',
|
|
104
|
+
},
|
|
105
|
+
dependencies: {
|
|
106
|
+
'@eide/extension-sdk': '^0.1.0',
|
|
107
|
+
react: '^19.0.0',
|
|
108
|
+
'react-dom': '^19.0.0',
|
|
109
|
+
},
|
|
110
|
+
devDependencies: {
|
|
111
|
+
'@tailwindcss/vite': '^4.0.6',
|
|
112
|
+
'@types/react': '^19.0.0',
|
|
113
|
+
'@types/react-dom': '^19.0.0',
|
|
114
|
+
'@vitejs/plugin-react': '^4.3.4',
|
|
115
|
+
tailwindcss: '^4.0.0',
|
|
116
|
+
typescript: '^5.0.0',
|
|
117
|
+
vite: '^6.0.7',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
121
|
+
}
|
|
122
|
+
function getUiTsconfig() {
|
|
123
|
+
const config = {
|
|
124
|
+
compilerOptions: {
|
|
125
|
+
target: 'ES2020',
|
|
126
|
+
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
|
|
127
|
+
module: 'ESNext',
|
|
128
|
+
skipLibCheck: true,
|
|
129
|
+
moduleResolution: 'bundler',
|
|
130
|
+
allowImportingTsExtensions: true,
|
|
131
|
+
resolveJsonModule: true,
|
|
132
|
+
isolatedModules: true,
|
|
133
|
+
noEmit: true,
|
|
134
|
+
jsx: 'react-jsx',
|
|
135
|
+
strict: true,
|
|
136
|
+
baseUrl: '.',
|
|
137
|
+
paths: { '@/*': ['./src/*'] },
|
|
138
|
+
},
|
|
139
|
+
include: ['src'],
|
|
140
|
+
};
|
|
141
|
+
return JSON.stringify(config, null, 2) + '\n';
|
|
142
|
+
}
|
|
143
|
+
function getUiViteConfig() {
|
|
144
|
+
return `import { defineConfig } from 'vite';
|
|
145
|
+
import react from '@vitejs/plugin-react';
|
|
146
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
147
|
+
import path from 'path';
|
|
148
|
+
|
|
149
|
+
export default defineConfig({
|
|
150
|
+
plugins: [react(), tailwindcss()],
|
|
151
|
+
resolve: {
|
|
152
|
+
alias: {
|
|
153
|
+
'@': path.resolve(__dirname, './src'),
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
server: {
|
|
157
|
+
port: 3001,
|
|
158
|
+
host: '127.0.0.1',
|
|
159
|
+
cors: true,
|
|
160
|
+
headers: {
|
|
161
|
+
'X-Frame-Options': 'ALLOWALL',
|
|
162
|
+
'Content-Security-Policy': 'frame-ancestors *',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
build: {
|
|
166
|
+
outDir: 'dist',
|
|
167
|
+
sourcemap: true,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
function getUiIndexHtml(name) {
|
|
173
|
+
const title = name
|
|
174
|
+
.replace(/-/g, ' ')
|
|
175
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
176
|
+
return `<!doctype html>
|
|
177
|
+
<html lang="en">
|
|
178
|
+
<head>
|
|
179
|
+
<meta charset="UTF-8" />
|
|
180
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
181
|
+
<title>${title}</title>
|
|
182
|
+
</head>
|
|
183
|
+
<body>
|
|
184
|
+
<div id="root"></div>
|
|
185
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
186
|
+
</body>
|
|
187
|
+
</html>
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
function getUiEnvExample(apiUrl) {
|
|
191
|
+
return `VITE_PARENT_ORIGIN=http://localhost:3000
|
|
192
|
+
VITE_API_URL=${apiUrl}
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
function getUiGitignore() {
|
|
196
|
+
return `node_modules
|
|
197
|
+
dist
|
|
198
|
+
.env
|
|
199
|
+
.env.local
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
function getUiMain() {
|
|
203
|
+
return `import { StrictMode } from 'react';
|
|
204
|
+
import { createRoot } from 'react-dom/client';
|
|
205
|
+
import { ExtensionProvider, useExtension } from '@eide/extension-sdk';
|
|
206
|
+
import { useEffect } from 'react';
|
|
207
|
+
import { App } from './App';
|
|
208
|
+
import './index.css';
|
|
209
|
+
|
|
210
|
+
function ThemeSync({ children }: { children: React.ReactNode }) {
|
|
211
|
+
const { theme } = useExtension();
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
const root = document.documentElement;
|
|
215
|
+
root.classList.remove('light', 'dark');
|
|
216
|
+
root.classList.add(theme);
|
|
217
|
+
}, [theme]);
|
|
218
|
+
|
|
219
|
+
return <>{children}</>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
createRoot(document.getElementById('root')!).render(
|
|
223
|
+
<StrictMode>
|
|
224
|
+
<ExtensionProvider>
|
|
225
|
+
<ThemeSync>
|
|
226
|
+
<App />
|
|
227
|
+
</ThemeSync>
|
|
228
|
+
</ExtensionProvider>
|
|
229
|
+
</StrictMode>
|
|
230
|
+
);
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
function getUiApp(extensionType) {
|
|
234
|
+
switch (extensionType) {
|
|
235
|
+
case 'custom-editor':
|
|
236
|
+
return getCustomEditorApp();
|
|
237
|
+
case 'widget':
|
|
238
|
+
return getWidgetApp();
|
|
239
|
+
case 'workflow':
|
|
240
|
+
return getWorkflowApp();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function getCustomEditorApp() {
|
|
244
|
+
return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
|
|
245
|
+
|
|
246
|
+
export function App() {
|
|
247
|
+
const { isReady, init, updateField, setDirty } = useExtension();
|
|
248
|
+
const containerRef = useAutoResize({ minHeight: 600 });
|
|
249
|
+
|
|
250
|
+
if (!isReady) return null;
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<div ref={containerRef} className="p-6 space-y-4">
|
|
254
|
+
<h1 className="text-lg font-semibold">
|
|
255
|
+
Editing: {init?.entityModelKey}
|
|
256
|
+
</h1>
|
|
257
|
+
<p className="text-sm text-gray-500">
|
|
258
|
+
Record: {init?.recordId}
|
|
259
|
+
</p>
|
|
260
|
+
{/* Add your editor form fields here */}
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
function getWidgetApp() {
|
|
267
|
+
return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
|
|
268
|
+
|
|
269
|
+
export function App() {
|
|
270
|
+
const { isReady, init, client } = useExtension();
|
|
271
|
+
const containerRef = useAutoResize({ minHeight: 300 });
|
|
272
|
+
|
|
273
|
+
if (!isReady) return null;
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<div ref={containerRef} className="p-6 space-y-4">
|
|
277
|
+
<h2 className="text-lg font-semibold">Dashboard Widget</h2>
|
|
278
|
+
<p className="text-sm text-gray-500">
|
|
279
|
+
Connected to: {init?.entityModelKey}
|
|
280
|
+
</p>
|
|
281
|
+
{/* Add your widget content here */}
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
function getWorkflowApp() {
|
|
288
|
+
return `import { useExtension, useAutoResize } from '@eide/extension-sdk';
|
|
289
|
+
|
|
290
|
+
export function App() {
|
|
291
|
+
const { isReady, init, client, requestSave } = useExtension();
|
|
292
|
+
const containerRef = useAutoResize({ minHeight: 400 });
|
|
293
|
+
|
|
294
|
+
if (!isReady) return null;
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div ref={containerRef} className="p-6 space-y-4">
|
|
298
|
+
<h1 className="text-lg font-semibold">Workflow Extension</h1>
|
|
299
|
+
<p className="text-sm text-gray-500">
|
|
300
|
+
Processing: {init?.entityModelKey} / {init?.recordId}
|
|
301
|
+
</p>
|
|
302
|
+
{/* Add your workflow steps here */}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
function getUiCss() {
|
|
309
|
+
return `@import 'tailwindcss';
|
|
310
|
+
|
|
311
|
+
:root {
|
|
312
|
+
--background: #ffffff;
|
|
313
|
+
--foreground: #171717;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.dark {
|
|
317
|
+
--background: #0a0a0a;
|
|
318
|
+
--foreground: #ededed;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
body {
|
|
322
|
+
background: var(--background);
|
|
323
|
+
color: var(--foreground);
|
|
324
|
+
font-family: system-ui, sans-serif;
|
|
325
|
+
}
|
|
326
|
+
`;
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// API (Hono)
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
function getApiPackageJson(name) {
|
|
332
|
+
const pkg = {
|
|
333
|
+
name: `${name}-api`,
|
|
334
|
+
version: '0.1.0',
|
|
335
|
+
private: true,
|
|
336
|
+
type: 'module',
|
|
337
|
+
scripts: {
|
|
338
|
+
dev: 'tsx watch src/index.ts',
|
|
339
|
+
build: 'tsx src/index.ts',
|
|
340
|
+
start: 'node dist/index.js',
|
|
341
|
+
},
|
|
342
|
+
dependencies: {
|
|
343
|
+
'@eide/extension-sdk': '^0.1.0',
|
|
344
|
+
hono: '^4.0.0',
|
|
345
|
+
'@hono/node-server': '^1.0.0',
|
|
346
|
+
},
|
|
347
|
+
devDependencies: {
|
|
348
|
+
tsx: '^4.0.0',
|
|
349
|
+
typescript: '^5.0.0',
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
353
|
+
}
|
|
354
|
+
function getApiTsconfig() {
|
|
355
|
+
const config = {
|
|
356
|
+
compilerOptions: {
|
|
357
|
+
target: 'ES2020',
|
|
358
|
+
module: 'ESNext',
|
|
359
|
+
moduleResolution: 'bundler',
|
|
360
|
+
outDir: './dist',
|
|
361
|
+
rootDir: './src',
|
|
362
|
+
strict: true,
|
|
363
|
+
esModuleInterop: true,
|
|
364
|
+
skipLibCheck: true,
|
|
365
|
+
resolveJsonModule: true,
|
|
366
|
+
isolatedModules: true,
|
|
367
|
+
},
|
|
368
|
+
include: ['src'],
|
|
369
|
+
exclude: ['dist', 'node_modules'],
|
|
370
|
+
};
|
|
371
|
+
return JSON.stringify(config, null, 2) + '\n';
|
|
372
|
+
}
|
|
373
|
+
function getApiEnvExample(apiUrl) {
|
|
374
|
+
const baseUrl = apiUrl.replace(/\/graphql$/, '');
|
|
375
|
+
return `# Platform API
|
|
376
|
+
PLATFORM_BASE_URL=${baseUrl}
|
|
377
|
+
PLATFORM_API_KEY=sk_your_api_key_here
|
|
378
|
+
|
|
379
|
+
# Extension
|
|
380
|
+
EXTENSION_KEY=${'{your-extension-key}'}
|
|
381
|
+
WEBHOOK_SECRET=your_webhook_secret_here
|
|
382
|
+
|
|
383
|
+
# Server
|
|
384
|
+
PORT=3002
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
function getApiIndex() {
|
|
388
|
+
return `import { Hono } from 'hono';
|
|
389
|
+
import { serve } from '@hono/node-server';
|
|
390
|
+
import { webhooks } from './routes/webhooks';
|
|
391
|
+
import { health } from './routes/health';
|
|
392
|
+
|
|
393
|
+
const app = new Hono();
|
|
394
|
+
|
|
395
|
+
// Routes
|
|
396
|
+
app.route('/webhooks', webhooks);
|
|
397
|
+
app.route('/', health);
|
|
398
|
+
|
|
399
|
+
const port = parseInt(process.env.PORT || '3002', 10);
|
|
400
|
+
|
|
401
|
+
console.log(\`Extension API running on http://localhost:\${port}\`);
|
|
402
|
+
|
|
403
|
+
serve({ fetch: app.fetch, port });
|
|
404
|
+
`;
|
|
405
|
+
}
|
|
406
|
+
function getApiWebhooks() {
|
|
407
|
+
return `import { Hono } from 'hono';
|
|
408
|
+
import { verifyWebhookSignature } from '@eide/extension-sdk/server';
|
|
409
|
+
|
|
410
|
+
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
|
|
411
|
+
|
|
412
|
+
export const webhooks = new Hono();
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Receive entity lifecycle events from the platform.
|
|
416
|
+
*/
|
|
417
|
+
webhooks.post('/entity-changed', async (c) => {
|
|
418
|
+
const body = await c.req.text();
|
|
419
|
+
const signature = c.req.header('x-eide-signature') ?? '';
|
|
420
|
+
|
|
421
|
+
const valid = await verifyWebhookSignature(body, signature, WEBHOOK_SECRET);
|
|
422
|
+
if (!valid) {
|
|
423
|
+
return c.json({ error: 'Invalid signature' }, 401);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const payload = JSON.parse(body);
|
|
427
|
+
console.log('[Webhook] Entity changed:', payload.event, payload.entityId);
|
|
428
|
+
|
|
429
|
+
// TODO: Handle the event (sync, transform, notify, etc.)
|
|
430
|
+
|
|
431
|
+
return c.json({ received: true });
|
|
432
|
+
});
|
|
433
|
+
`;
|
|
434
|
+
}
|
|
435
|
+
function getApiHealth() {
|
|
436
|
+
return `import { Hono } from 'hono';
|
|
437
|
+
|
|
438
|
+
export const health = new Hono();
|
|
439
|
+
|
|
440
|
+
health.get('/health', (c) => {
|
|
441
|
+
return c.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
442
|
+
});
|
|
443
|
+
`;
|
|
444
|
+
}
|
|
445
|
+
function getApiPlatform() {
|
|
446
|
+
return `import { createExtensionClient } from '@eide/extension-sdk/server';
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Pre-configured platform client for this extension.
|
|
450
|
+
*
|
|
451
|
+
* Uses env vars for configuration:
|
|
452
|
+
* - PLATFORM_BASE_URL: Platform API base URL
|
|
453
|
+
* - PLATFORM_API_KEY: Project-scoped API key (sk_*)
|
|
454
|
+
* - EXTENSION_KEY: This extension's extension key
|
|
455
|
+
*/
|
|
456
|
+
export const platform = createExtensionClient({
|
|
457
|
+
baseUrl: process.env.PLATFORM_BASE_URL || 'http://localhost:4000',
|
|
458
|
+
apiKey: process.env.PLATFORM_API_KEY || '',
|
|
459
|
+
extensionKey: process.env.EXTENSION_KEY || '',
|
|
460
|
+
});
|
|
461
|
+
`;
|
|
462
|
+
}
|