@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 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,CAezB;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"}
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,aAsBhC,CAAC;AAEH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CA0D1D,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,CAwCR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAc3E;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,EAAE,GAC1B,GAAG,CAAC,MAAM,CAAC,CAiBb;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,CAiFjE"}
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 ((field.type === 'list' || field.type === 'tree') &&
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 ((field.type === 'list' || field.type === 'tree') &&
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 ((field.type === 'list' || field.type === 'tree') &&
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 ((field.type === 'list' || field.type === 'tree') &&
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,CAgBhD"}
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/tree itemType refs
122
- if ((field.type === 'list' || field.type === 'tree') &&
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 ((field.type === 'list' || field.type === 'tree') &&
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;AAssB1D,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"}
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
- token
12
+ success
13
+ accessToken
13
14
  refreshToken
14
- customer { id email status }
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
- token
21
+ success
22
+ accessToken
21
23
  refreshToken
22
- customer { id email status }
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
- token
39
+ success
40
+ accessToken
37
41
  refreshToken
38
- customer { id email status }
42
+ user { id email status }
39
43
  }
40
44
  }
41
45
 
42
46
  mutation CustomerRefreshToken($refreshToken: String!) {
43
47
  customerRefreshToken(refreshToken: $refreshToken) {
44
- token
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
- alreadyVerified
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
- enabledMethods
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
- registrationEnabled
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
- type
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
- message
143
- requiresOTP
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
- token
169
+ accessToken
150
170
  refreshToken
151
- customer { id email status }
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
- token
180
+ accessToken
159
181
  refreshToken
160
- customer { id email status }
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
- folder
177
- fileUrl
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
- fileUrl
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
- isRead
296
- readAt
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
- resourceId
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
- record {
660
- id
661
- modelKey
662
- naturalKey
663
- data
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
- exists
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,CAoIrE,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"}
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,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { GlobalOptions } from '../lib/config.js';
3
+ export declare function registerCreateExtensionCommand(program: Command, globalOpts: () => GlobalOptions): void;
4
+ //# sourceMappingURL=create-extension.d.ts.map
@@ -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,4 @@
1
+ type ExtensionType = 'custom-editor' | 'workflow' | 'widget';
2
+ export declare function scaffold(projectName: string, extensionType: ExtensionType, apiUrl: string): Promise<void>;
3
+ export {};
4
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eide/foir-cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Universal platform CLI for EIDE — scriptable, composable resource management",
5
5
  "type": "module",
6
6
  "publishConfig": {