@eide/foir-cli 0.1.2 → 0.1.4
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 +40 -0
- package/dist/codegen/fetch-models.d.ts.map +1 -0
- package/dist/codegen/fetch-models.js +45 -0
- package/dist/codegen/field-mapping.d.ts +28 -0
- package/dist/codegen/field-mapping.d.ts.map +1 -0
- package/dist/codegen/field-mapping.js +194 -0
- package/dist/codegen/generators/config.d.ts +5 -0
- package/dist/codegen/generators/config.d.ts.map +1 -0
- package/dist/codegen/generators/config.js +82 -0
- package/dist/codegen/generators/documents.d.ts +6 -0
- package/dist/codegen/generators/documents.d.ts.map +1 -0
- package/dist/codegen/generators/documents.js +91 -0
- package/dist/codegen/generators/field-types.d.ts +5 -0
- package/dist/codegen/generators/field-types.d.ts.map +1 -0
- package/dist/codegen/generators/field-types.js +339 -0
- package/dist/codegen/generators/model-index.d.ts +6 -0
- package/dist/codegen/generators/model-index.d.ts.map +1 -0
- package/dist/codegen/generators/model-index.js +26 -0
- package/dist/codegen/generators/model-types.d.ts +12 -0
- package/dist/codegen/generators/model-types.d.ts.map +1 -0
- package/dist/codegen/generators/model-types.js +150 -0
- package/dist/codegen/generators/swift-field-types.d.ts +5 -0
- package/dist/codegen/generators/swift-field-types.d.ts.map +1 -0
- package/dist/codegen/generators/swift-field-types.js +145 -0
- package/dist/codegen/generators/swift-model-keys.d.ts +6 -0
- package/dist/codegen/generators/swift-model-keys.d.ts.map +1 -0
- package/dist/codegen/generators/swift-model-keys.js +25 -0
- package/dist/codegen/generators/swift-types.d.ts +13 -0
- package/dist/codegen/generators/swift-types.d.ts.map +1 -0
- package/dist/codegen/generators/swift-types.js +184 -0
- package/dist/codegen/swift-field-mapping.d.ts +30 -0
- package/dist/codegen/swift-field-mapping.d.ts.map +1 -0
- package/dist/codegen/swift-field-mapping.js +156 -0
- package/dist/codegen/write-files.d.ts +15 -0
- package/dist/codegen/write-files.d.ts.map +1 -0
- package/dist/codegen/write-files.js +36 -0
- package/dist/commands/models.d.ts.map +1 -1
- package/dist/commands/models.js +0 -6
- package/dist/commands/pull.d.ts +4 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +150 -0
- package/dist/config/pull-config.d.ts +27 -0
- package/dist/config/pull-config.d.ts.map +1 -0
- package/dist/config/pull-config.js +66 -0
- package/dist/config/types.d.ts +33 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +15 -0
- package/dist/graphql/queries.d.ts +1 -0
- package/dist/graphql/queries.d.ts.map +1 -1
- package/dist/graphql/queries.js +8 -0
- package/package.json +9 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-model-keys.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/swift-model-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAwBrE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates ModelKeys.swift with all model key constants in one enum.
|
|
3
|
+
*/
|
|
4
|
+
import { toCamelCase } from '../field-mapping.js';
|
|
5
|
+
export function generateSwiftModelKeys(models) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
lines.push('//');
|
|
8
|
+
lines.push('// ModelKeys.swift');
|
|
9
|
+
lines.push('//');
|
|
10
|
+
lines.push('// All model key constants.');
|
|
11
|
+
lines.push('//');
|
|
12
|
+
lines.push('// @generated by foir \u2014 DO NOT EDIT MANUALLY');
|
|
13
|
+
lines.push('//');
|
|
14
|
+
lines.push('');
|
|
15
|
+
lines.push('import Foundation');
|
|
16
|
+
lines.push('');
|
|
17
|
+
lines.push('enum ModelKeys {');
|
|
18
|
+
for (const model of models) {
|
|
19
|
+
const propName = toCamelCase(model.key);
|
|
20
|
+
lines.push(` static let ${propName} = "${model.key}"`);
|
|
21
|
+
}
|
|
22
|
+
lines.push('}');
|
|
23
|
+
lines.push('');
|
|
24
|
+
return lines.join('\n');
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates per-model Swift files with:
|
|
3
|
+
* - Field key enum (compile-time safe field keys)
|
|
4
|
+
* - Data struct (typed representation)
|
|
5
|
+
* - Serialization helpers (toSyncData / fromSyncData)
|
|
6
|
+
* - Model config constant
|
|
7
|
+
*/
|
|
8
|
+
import type { CodegenModel } from '../fetch-models.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generate a complete Swift file for a single model.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateSwiftModelFile(model: CodegenModel): string;
|
|
13
|
+
//# sourceMappingURL=swift-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-types.d.ts","sourceRoot":"","sources":["../../../src/codegen/generators/swift-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAkClE"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates per-model Swift files with:
|
|
3
|
+
* - Field key enum (compile-time safe field keys)
|
|
4
|
+
* - Data struct (typed representation)
|
|
5
|
+
* - Serialization helpers (toSyncData / fromSyncData)
|
|
6
|
+
* - Model config constant
|
|
7
|
+
*/
|
|
8
|
+
import { toPascalCase } from '../field-mapping.js';
|
|
9
|
+
import { getSwiftFieldType, SWIFT_FIELD_TYPE_MAPPING } from '../swift-field-mapping.js';
|
|
10
|
+
/**
|
|
11
|
+
* Generate a complete Swift file for a single model.
|
|
12
|
+
*/
|
|
13
|
+
export function generateSwiftModelFile(model) {
|
|
14
|
+
const typeName = toPascalCase(model.key);
|
|
15
|
+
const fields = model.fields ?? [];
|
|
16
|
+
const lines = [];
|
|
17
|
+
// Header
|
|
18
|
+
lines.push('//');
|
|
19
|
+
lines.push(`// ${typeName}.swift`);
|
|
20
|
+
lines.push('//');
|
|
21
|
+
lines.push(`// Generated from model '${model.key}'`);
|
|
22
|
+
lines.push('//');
|
|
23
|
+
lines.push('// @generated by foir \u2014 DO NOT EDIT MANUALLY');
|
|
24
|
+
lines.push('//');
|
|
25
|
+
lines.push('');
|
|
26
|
+
lines.push('import Foundation');
|
|
27
|
+
lines.push('');
|
|
28
|
+
// 1. Field key enum
|
|
29
|
+
lines.push(generateFieldsEnum(typeName, fields));
|
|
30
|
+
lines.push('');
|
|
31
|
+
// 2. Data struct
|
|
32
|
+
lines.push(generateDataStruct(typeName, fields));
|
|
33
|
+
lines.push('');
|
|
34
|
+
// 3. Serialization extension
|
|
35
|
+
lines.push(generateSerializationExtension(typeName, fields));
|
|
36
|
+
lines.push('');
|
|
37
|
+
// 4. Config enum
|
|
38
|
+
lines.push(generateConfigEnum(typeName, model));
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
function generateFieldsEnum(typeName, fields) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
lines.push(`// MARK: - ${typeName} Field Keys`);
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push(`enum ${typeName}Fields {`);
|
|
46
|
+
for (const field of fields) {
|
|
47
|
+
lines.push(` static let ${field.key} = "${field.key}"`);
|
|
48
|
+
}
|
|
49
|
+
lines.push('}');
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
function generateDataStruct(typeName, fields) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
lines.push(`// MARK: - ${typeName} Data`);
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push(`struct ${typeName}Data {`);
|
|
57
|
+
for (const field of fields) {
|
|
58
|
+
const { type, isOptional } = getSwiftFieldType(field);
|
|
59
|
+
const optionalSuffix = isOptional ? '?' : '';
|
|
60
|
+
lines.push(` var ${field.key}: ${type}${optionalSuffix}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('}');
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
function generateSerializationExtension(typeName, fields) {
|
|
66
|
+
const lines = [];
|
|
67
|
+
lines.push(`// MARK: - ${typeName} Serialization`);
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(`extension ${typeName}Data {`);
|
|
70
|
+
// toSyncData()
|
|
71
|
+
lines.push(' func toSyncData() -> [String: Any] {');
|
|
72
|
+
// Find the first required (non-optional) field to seed the dictionary
|
|
73
|
+
const requiredFields = fields.filter((f) => {
|
|
74
|
+
const { isOptional } = getSwiftFieldType(f);
|
|
75
|
+
return !isOptional;
|
|
76
|
+
});
|
|
77
|
+
const optionalFields = fields.filter((f) => {
|
|
78
|
+
const { isOptional } = getSwiftFieldType(f);
|
|
79
|
+
return isOptional;
|
|
80
|
+
});
|
|
81
|
+
if (requiredFields.length > 0) {
|
|
82
|
+
if (optionalFields.length === 0) {
|
|
83
|
+
// All fields are required — return inline
|
|
84
|
+
lines.push(` return [`);
|
|
85
|
+
requiredFields.forEach((f, i) => {
|
|
86
|
+
const comma = i < requiredFields.length - 1 ? ',' : '';
|
|
87
|
+
lines.push(` ${typeName}Fields.${f.key}: ${toSyncValueExpr(f)}${comma}`);
|
|
88
|
+
});
|
|
89
|
+
lines.push(' ]');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
lines.push(` var data: [String: Any] = [`);
|
|
93
|
+
requiredFields.forEach((f, i) => {
|
|
94
|
+
const comma = i < requiredFields.length - 1 ? ',' : '';
|
|
95
|
+
lines.push(` ${typeName}Fields.${f.key}: ${toSyncValueExpr(f)}${comma}`);
|
|
96
|
+
});
|
|
97
|
+
lines.push(' ]');
|
|
98
|
+
for (const f of optionalFields) {
|
|
99
|
+
lines.push(` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional(f)} }`);
|
|
100
|
+
}
|
|
101
|
+
lines.push(' return data');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// All fields are optional
|
|
106
|
+
lines.push(' var data: [String: Any] = [:]');
|
|
107
|
+
for (const f of optionalFields) {
|
|
108
|
+
lines.push(` if let ${f.key} { data[${typeName}Fields.${f.key}] = ${toSyncValueExprForOptional(f)} }`);
|
|
109
|
+
}
|
|
110
|
+
lines.push(' return data');
|
|
111
|
+
}
|
|
112
|
+
lines.push(' }');
|
|
113
|
+
lines.push('');
|
|
114
|
+
// fromSyncData()
|
|
115
|
+
lines.push(' static func fromSyncData(_ data: [String: Any]) -> ' + typeName + 'Data {');
|
|
116
|
+
lines.push(` ${typeName}Data(`);
|
|
117
|
+
fields.forEach((field, i) => {
|
|
118
|
+
const comma = i < fields.length - 1 ? ',' : '';
|
|
119
|
+
const { isOptional, mapping } = getSwiftFieldType(field);
|
|
120
|
+
lines.push(` ${field.key}: ${fromSyncDataExpr(field, typeName, isOptional, mapping)}${comma}`);
|
|
121
|
+
});
|
|
122
|
+
lines.push(' )');
|
|
123
|
+
lines.push(' }');
|
|
124
|
+
lines.push('}');
|
|
125
|
+
return lines.join('\n');
|
|
126
|
+
}
|
|
127
|
+
/** Expression for a required field value in toSyncData */
|
|
128
|
+
function toSyncValueExpr(field) {
|
|
129
|
+
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
130
|
+
if (mapping?.needsSharedType) {
|
|
131
|
+
return `${field.key}.toSyncData()`;
|
|
132
|
+
}
|
|
133
|
+
return field.key;
|
|
134
|
+
}
|
|
135
|
+
/** Expression for an optional field value in toSyncData (inside `if let`) */
|
|
136
|
+
function toSyncValueExprForOptional(field) {
|
|
137
|
+
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
138
|
+
if (mapping?.needsSharedType) {
|
|
139
|
+
return `${field.key}.toSyncData()`;
|
|
140
|
+
}
|
|
141
|
+
return field.key;
|
|
142
|
+
}
|
|
143
|
+
/** Expression for reading a field from sync data dictionary */
|
|
144
|
+
function fromSyncDataExpr(field, typeName, isOptional, mapping) {
|
|
145
|
+
const accessor = `data[${typeName}Fields.${field.key}]`;
|
|
146
|
+
if (!mapping) {
|
|
147
|
+
// Unknown type
|
|
148
|
+
return isOptional ? `${accessor}` : `${accessor} ?? nil`;
|
|
149
|
+
}
|
|
150
|
+
// Types with shared structs need .fromSyncData conversion
|
|
151
|
+
if (mapping.needsSharedType) {
|
|
152
|
+
const dictCast = `${accessor} as? [String: Any]`;
|
|
153
|
+
if (isOptional) {
|
|
154
|
+
return `(${dictCast}).map { ${mapping.type}.fromSyncData($0) }`;
|
|
155
|
+
}
|
|
156
|
+
return `${mapping.type}.fromSyncData(${dictCast} ?? [:])`;
|
|
157
|
+
}
|
|
158
|
+
// json → Any
|
|
159
|
+
if (field.type === 'json') {
|
|
160
|
+
return isOptional ? accessor : `${accessor}`;
|
|
161
|
+
}
|
|
162
|
+
// Simple cast types
|
|
163
|
+
if (isOptional) {
|
|
164
|
+
return `${accessor} ${mapping.castExpression}`;
|
|
165
|
+
}
|
|
166
|
+
return `${accessor} ${mapping.castExpression} ?? ${mapping.defaultValue}`;
|
|
167
|
+
}
|
|
168
|
+
function generateConfigEnum(typeName, model) {
|
|
169
|
+
const lines = [];
|
|
170
|
+
lines.push(`// MARK: - ${typeName} Config`);
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push(`enum ${typeName}Config {`);
|
|
173
|
+
const escapedName = (model.name ?? model.key).replace(/"/g, '\\"');
|
|
174
|
+
lines.push(` static let key = "${model.key}"`);
|
|
175
|
+
lines.push(` static let name = "${escapedName}"`);
|
|
176
|
+
lines.push(` static let customerScoped = ${model.config.customerScoped}`);
|
|
177
|
+
lines.push(` static let publicApi = ${model.config.publicApi}`);
|
|
178
|
+
lines.push(` static let versioning = ${model.config.versioning}`);
|
|
179
|
+
lines.push(` static let publishing = ${model.config.publishing}`);
|
|
180
|
+
lines.push(` static let variants = ${model.config.variants}`);
|
|
181
|
+
lines.push('}');
|
|
182
|
+
lines.push('');
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps EIDE field types to Swift types for sync data serialization.
|
|
3
|
+
*
|
|
4
|
+
* Reference fields map to String (not a struct) because sync data
|
|
5
|
+
* payloads carry plain string IDs — the platform resolves references separately.
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldSchemaForGen } from './field-mapping.js';
|
|
8
|
+
export interface SwiftTypeMapping {
|
|
9
|
+
/** Swift type name */
|
|
10
|
+
type: string;
|
|
11
|
+
/** Whether the field is always optional (regardless of required flag) */
|
|
12
|
+
alwaysOptional: boolean;
|
|
13
|
+
/** Default value expression for non-optional required fields in fromSyncData */
|
|
14
|
+
defaultValue: string;
|
|
15
|
+
/** The `as?` cast expression for fromSyncData */
|
|
16
|
+
castExpression: string;
|
|
17
|
+
/** Whether this type needs a shared struct in FieldTypes.swift */
|
|
18
|
+
needsSharedType?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const SWIFT_FIELD_TYPE_MAPPING: Record<string, SwiftTypeMapping>;
|
|
21
|
+
/**
|
|
22
|
+
* Get the Swift type for a field.
|
|
23
|
+
* Returns { type, isOptional } so callers can decide on `?` suffix.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getSwiftFieldType(field: FieldSchemaForGen): {
|
|
26
|
+
type: string;
|
|
27
|
+
isOptional: boolean;
|
|
28
|
+
mapping: SwiftTypeMapping | undefined;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=swift-field-mapping.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps EIDE field types to Swift types for sync data serialization.
|
|
3
|
+
*
|
|
4
|
+
* Reference fields map to String (not a struct) because sync data
|
|
5
|
+
* payloads carry plain string IDs — the platform resolves references separately.
|
|
6
|
+
*/
|
|
7
|
+
export const SWIFT_FIELD_TYPE_MAPPING = {
|
|
8
|
+
text: {
|
|
9
|
+
type: 'String',
|
|
10
|
+
alwaysOptional: false,
|
|
11
|
+
defaultValue: '""',
|
|
12
|
+
castExpression: 'as? String',
|
|
13
|
+
},
|
|
14
|
+
richtext: {
|
|
15
|
+
type: 'String',
|
|
16
|
+
alwaysOptional: true,
|
|
17
|
+
defaultValue: '""',
|
|
18
|
+
castExpression: 'as? String',
|
|
19
|
+
},
|
|
20
|
+
number: {
|
|
21
|
+
type: 'Double',
|
|
22
|
+
alwaysOptional: true,
|
|
23
|
+
defaultValue: '0',
|
|
24
|
+
castExpression: 'as? Double',
|
|
25
|
+
},
|
|
26
|
+
boolean: {
|
|
27
|
+
type: 'Bool',
|
|
28
|
+
alwaysOptional: true,
|
|
29
|
+
defaultValue: 'false',
|
|
30
|
+
castExpression: 'as? Bool',
|
|
31
|
+
},
|
|
32
|
+
email: {
|
|
33
|
+
type: 'String',
|
|
34
|
+
alwaysOptional: true,
|
|
35
|
+
defaultValue: '""',
|
|
36
|
+
castExpression: 'as? String',
|
|
37
|
+
},
|
|
38
|
+
phone: {
|
|
39
|
+
type: 'String',
|
|
40
|
+
alwaysOptional: true,
|
|
41
|
+
defaultValue: '""',
|
|
42
|
+
castExpression: 'as? String',
|
|
43
|
+
},
|
|
44
|
+
url: {
|
|
45
|
+
type: 'String',
|
|
46
|
+
alwaysOptional: true,
|
|
47
|
+
defaultValue: '""',
|
|
48
|
+
castExpression: 'as? String',
|
|
49
|
+
},
|
|
50
|
+
date: {
|
|
51
|
+
type: 'String',
|
|
52
|
+
alwaysOptional: true,
|
|
53
|
+
defaultValue: '""',
|
|
54
|
+
castExpression: 'as? String',
|
|
55
|
+
},
|
|
56
|
+
image: {
|
|
57
|
+
type: 'ImageValue',
|
|
58
|
+
alwaysOptional: true,
|
|
59
|
+
defaultValue: 'ImageValue(id: "", url: "")',
|
|
60
|
+
castExpression: 'as? [String: Any]',
|
|
61
|
+
needsSharedType: true,
|
|
62
|
+
},
|
|
63
|
+
video: {
|
|
64
|
+
type: 'VideoValue',
|
|
65
|
+
alwaysOptional: true,
|
|
66
|
+
defaultValue: 'VideoValue(id: "", url: "")',
|
|
67
|
+
castExpression: 'as? [String: Any]',
|
|
68
|
+
needsSharedType: true,
|
|
69
|
+
},
|
|
70
|
+
file: {
|
|
71
|
+
type: 'FileValue',
|
|
72
|
+
alwaysOptional: true,
|
|
73
|
+
defaultValue: 'FileValue(id: "", url: "", name: "", size: 0, mimeType: "")',
|
|
74
|
+
castExpression: 'as? [String: Any]',
|
|
75
|
+
needsSharedType: true,
|
|
76
|
+
},
|
|
77
|
+
currency: {
|
|
78
|
+
type: 'CurrencyValue',
|
|
79
|
+
alwaysOptional: true,
|
|
80
|
+
defaultValue: 'CurrencyValue(amount: 0, currency: "")',
|
|
81
|
+
castExpression: 'as? [String: Any]',
|
|
82
|
+
needsSharedType: true,
|
|
83
|
+
},
|
|
84
|
+
select: {
|
|
85
|
+
type: 'String',
|
|
86
|
+
alwaysOptional: true,
|
|
87
|
+
defaultValue: '""',
|
|
88
|
+
castExpression: 'as? String',
|
|
89
|
+
},
|
|
90
|
+
multiselect: {
|
|
91
|
+
type: '[String]',
|
|
92
|
+
alwaysOptional: true,
|
|
93
|
+
defaultValue: '[]',
|
|
94
|
+
castExpression: 'as? [String]',
|
|
95
|
+
},
|
|
96
|
+
json: {
|
|
97
|
+
type: 'Any',
|
|
98
|
+
alwaysOptional: true,
|
|
99
|
+
defaultValue: 'nil',
|
|
100
|
+
castExpression: '',
|
|
101
|
+
},
|
|
102
|
+
list: {
|
|
103
|
+
type: '[Any]',
|
|
104
|
+
alwaysOptional: true,
|
|
105
|
+
defaultValue: '[]',
|
|
106
|
+
castExpression: 'as? [Any]',
|
|
107
|
+
},
|
|
108
|
+
tree: {
|
|
109
|
+
type: '[Any]',
|
|
110
|
+
alwaysOptional: true,
|
|
111
|
+
defaultValue: '[]',
|
|
112
|
+
castExpression: 'as? [Any]',
|
|
113
|
+
},
|
|
114
|
+
flexible: {
|
|
115
|
+
type: '[[String: Any]]',
|
|
116
|
+
alwaysOptional: true,
|
|
117
|
+
defaultValue: '[]',
|
|
118
|
+
castExpression: 'as? [[String: Any]]',
|
|
119
|
+
},
|
|
120
|
+
'entity-reference': {
|
|
121
|
+
type: 'String',
|
|
122
|
+
alwaysOptional: true,
|
|
123
|
+
defaultValue: '""',
|
|
124
|
+
castExpression: 'as? String',
|
|
125
|
+
},
|
|
126
|
+
reference: {
|
|
127
|
+
type: 'String',
|
|
128
|
+
alwaysOptional: true,
|
|
129
|
+
defaultValue: '""',
|
|
130
|
+
castExpression: 'as? String',
|
|
131
|
+
},
|
|
132
|
+
link: {
|
|
133
|
+
type: 'LinkValue',
|
|
134
|
+
alwaysOptional: true,
|
|
135
|
+
defaultValue: 'LinkValue(type: "")',
|
|
136
|
+
castExpression: 'as? [String: Any]',
|
|
137
|
+
needsSharedType: true,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Get the Swift type for a field.
|
|
142
|
+
* Returns { type, isOptional } so callers can decide on `?` suffix.
|
|
143
|
+
*/
|
|
144
|
+
export function getSwiftFieldType(field) {
|
|
145
|
+
const mapping = SWIFT_FIELD_TYPE_MAPPING[field.type];
|
|
146
|
+
if (!mapping) {
|
|
147
|
+
// Unknown type — treat as optional Any
|
|
148
|
+
return {
|
|
149
|
+
type: 'Any',
|
|
150
|
+
isOptional: true,
|
|
151
|
+
mapping: undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const isOptional = mapping.alwaysOptional || !field.required;
|
|
155
|
+
return { type: mapping.type, isOptional, mapping };
|
|
156
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File writer with optional Prettier formatting.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Write a generated file to disk with optional Prettier formatting.
|
|
6
|
+
*/
|
|
7
|
+
export declare function writeGeneratedFile(filePath: string, content: string, usePrettier?: boolean): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Write multiple files in batch.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeFiles(files: Array<{
|
|
12
|
+
path: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}>, usePrettier?: boolean): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=write-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-files.d.ts","sourceRoot":"","sources":["../../src/codegen/write-files.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,OAAc,GAC1B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,WAAW,GAAE,OAAc,GAC1B,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File writer with optional Prettier formatting.
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Write a generated file to disk with optional Prettier formatting.
|
|
8
|
+
*/
|
|
9
|
+
export async function writeGeneratedFile(filePath, content, usePrettier = true) {
|
|
10
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
11
|
+
let formattedContent = content;
|
|
12
|
+
const isSwift = filePath.endsWith('.swift');
|
|
13
|
+
if (usePrettier && !isSwift) {
|
|
14
|
+
try {
|
|
15
|
+
const prettier = await import('prettier');
|
|
16
|
+
const parser = filePath.endsWith('.graphql') ? 'graphql' : 'typescript';
|
|
17
|
+
formattedContent = await prettier.format(content, {
|
|
18
|
+
parser,
|
|
19
|
+
semi: true,
|
|
20
|
+
singleQuote: true,
|
|
21
|
+
trailingComma: 'es5',
|
|
22
|
+
printWidth: 100,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Prettier not available or formatting failed — write unformatted
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
await writeFile(filePath, formattedContent, 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Write multiple files in batch.
|
|
33
|
+
*/
|
|
34
|
+
export async function writeFiles(files, usePrettier = true) {
|
|
35
|
+
await Promise.all(files.map((file) => writeGeneratedFile(file.path, file.content, usePrettier)));
|
|
36
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/commands/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AActD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/commands/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AActD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CAiMN"}
|
package/dist/commands/models.js
CHANGED
|
@@ -27,12 +27,6 @@ export function registerModelsCommands(program, globalOpts) {
|
|
|
27
27
|
{ key: 'key', header: 'Key', width: 24 },
|
|
28
28
|
{ key: 'name', header: 'Name', width: 24 },
|
|
29
29
|
{ key: 'category', header: 'Category', width: 14 },
|
|
30
|
-
{
|
|
31
|
-
key: 'systemEntity',
|
|
32
|
-
header: 'System',
|
|
33
|
-
width: 8,
|
|
34
|
-
format: (v) => (v ? 'yes' : 'no'),
|
|
35
|
-
},
|
|
36
30
|
{
|
|
37
31
|
key: 'updatedAt',
|
|
38
32
|
header: 'Updated',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAgBtD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,aAAa,GAC9B,IAAI,CAmJN"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { withErrorHandler } from '../lib/errors.js';
|
|
4
|
+
import { createClient } from '../lib/client.js';
|
|
5
|
+
import { loadPullConfig } from '../config/pull-config.js';
|
|
6
|
+
import { fetchModelsForCodegen, filterModels } from '../codegen/fetch-models.js';
|
|
7
|
+
import { toPascalCase } from '../codegen/field-mapping.js';
|
|
8
|
+
import { generateFieldTypesFile } from '../codegen/generators/field-types.js';
|
|
9
|
+
import { generateConfigFile } from '../codegen/generators/config.js';
|
|
10
|
+
import { generateModelTypes } from '../codegen/generators/model-types.js';
|
|
11
|
+
import { generateModelIndex } from '../codegen/generators/model-index.js';
|
|
12
|
+
import { generateModelDocuments } from '../codegen/generators/documents.js';
|
|
13
|
+
import { generateSwiftModelFile } from '../codegen/generators/swift-types.js';
|
|
14
|
+
import { generateSwiftFieldTypesFile } from '../codegen/generators/swift-field-types.js';
|
|
15
|
+
import { generateSwiftModelKeys } from '../codegen/generators/swift-model-keys.js';
|
|
16
|
+
import { writeFiles } from '../codegen/write-files.js';
|
|
17
|
+
export function registerPullCommand(program, globalOpts) {
|
|
18
|
+
program
|
|
19
|
+
.command('pull')
|
|
20
|
+
.description('Generate TypeScript types, GraphQL documents, and Swift types from platform models')
|
|
21
|
+
.option('--config <path>', 'Path to config file')
|
|
22
|
+
.option('--only <models>', 'Comma-separated model keys to generate')
|
|
23
|
+
.option('--no-prettier', 'Skip Prettier formatting')
|
|
24
|
+
.option('--dry-run', 'Show what would be generated without writing')
|
|
25
|
+
.option('--out <dir>', 'Override output directory for types')
|
|
26
|
+
.option('--swift <dir>', 'Generate Swift files to directory')
|
|
27
|
+
.action(withErrorHandler(globalOpts, async (cmdOpts) => {
|
|
28
|
+
const opts = globalOpts();
|
|
29
|
+
const flags = {
|
|
30
|
+
config: cmdOpts.config,
|
|
31
|
+
only: cmdOpts.only,
|
|
32
|
+
noPrettier: cmdOpts.prettier === false,
|
|
33
|
+
dryRun: !!cmdOpts.dryRun,
|
|
34
|
+
out: cmdOpts.out,
|
|
35
|
+
swift: cmdOpts.swift,
|
|
36
|
+
};
|
|
37
|
+
const config = await loadPullConfig(flags);
|
|
38
|
+
// Fetch models
|
|
39
|
+
const client = await createClient(opts);
|
|
40
|
+
console.log(chalk.dim('Fetching models…'));
|
|
41
|
+
const allModels = await fetchModelsForCodegen(client);
|
|
42
|
+
if (allModels.length === 0) {
|
|
43
|
+
console.log(chalk.yellow('No models found. Nothing to generate.'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const models = filterModels(allModels, {
|
|
47
|
+
only: config.only.length > 0 ? config.only : undefined,
|
|
48
|
+
includeInline: config.includeInline,
|
|
49
|
+
});
|
|
50
|
+
console.log(chalk.dim(`Found ${allModels.length} model(s), generating for ${models.length}.`));
|
|
51
|
+
// Collect files to write
|
|
52
|
+
const cwd = process.cwd();
|
|
53
|
+
const typesDir = resolve(cwd, config.output.types);
|
|
54
|
+
const docsDir = resolve(cwd, config.output.documents);
|
|
55
|
+
const files = [];
|
|
56
|
+
// 1. Static files
|
|
57
|
+
files.push({
|
|
58
|
+
path: resolve(typesDir, 'field-types.ts'),
|
|
59
|
+
content: generateFieldTypesFile(),
|
|
60
|
+
});
|
|
61
|
+
files.push({
|
|
62
|
+
path: resolve(typesDir, 'config.ts'),
|
|
63
|
+
content: generateConfigFile(),
|
|
64
|
+
});
|
|
65
|
+
// 2. Per-model type files
|
|
66
|
+
for (const model of models) {
|
|
67
|
+
files.push({
|
|
68
|
+
path: resolve(typesDir, 'models', `${model.key}.ts`),
|
|
69
|
+
content: generateModelTypes(model, models),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// 3. Model index
|
|
73
|
+
files.push({
|
|
74
|
+
path: resolve(typesDir, 'models', 'index.ts'),
|
|
75
|
+
content: generateModelIndex(models),
|
|
76
|
+
});
|
|
77
|
+
// 4. Per-model GraphQL documents (only for publicApi models with records)
|
|
78
|
+
const publicModels = models.filter((m) => m.config.publicApi && m.config.records);
|
|
79
|
+
for (const model of publicModels) {
|
|
80
|
+
files.push({
|
|
81
|
+
path: resolve(docsDir, `${model.key}.graphql`),
|
|
82
|
+
content: generateModelDocuments(model),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// 5. Main index
|
|
86
|
+
files.push({
|
|
87
|
+
path: resolve(typesDir, 'index.ts'),
|
|
88
|
+
content: generateMainIndex(),
|
|
89
|
+
});
|
|
90
|
+
// 6. Swift output (when configured)
|
|
91
|
+
if (config.output.swift) {
|
|
92
|
+
const swiftDir = resolve(cwd, config.output.swift);
|
|
93
|
+
// Shared types
|
|
94
|
+
files.push({
|
|
95
|
+
path: resolve(swiftDir, 'FieldTypes.swift'),
|
|
96
|
+
content: generateSwiftFieldTypesFile(),
|
|
97
|
+
});
|
|
98
|
+
// Model keys
|
|
99
|
+
files.push({
|
|
100
|
+
path: resolve(swiftDir, 'ModelKeys.swift'),
|
|
101
|
+
content: generateSwiftModelKeys(models),
|
|
102
|
+
});
|
|
103
|
+
// Per-model Swift files
|
|
104
|
+
for (const model of models) {
|
|
105
|
+
const swiftTypeName = toPascalCase(model.key);
|
|
106
|
+
files.push({
|
|
107
|
+
path: resolve(swiftDir, `${swiftTypeName}.swift`),
|
|
108
|
+
content: generateSwiftModelFile(model),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Dry run: list files
|
|
113
|
+
if (config.dryRun) {
|
|
114
|
+
console.log(chalk.bold('\nDry run — files that would be generated:\n'));
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
const rel = file.path.replace(cwd + '/', '');
|
|
117
|
+
console.log(` ${chalk.green('+')} ${rel}`);
|
|
118
|
+
}
|
|
119
|
+
console.log(`\n${chalk.dim(`${files.length} file(s) total`)}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Write files
|
|
123
|
+
await writeFiles(files, config.prettier);
|
|
124
|
+
// Summary
|
|
125
|
+
const modelCount = models.length;
|
|
126
|
+
const docCount = publicModels.length;
|
|
127
|
+
const swiftCount = config.output.swift ? models.length + 2 : 0; // +2 for FieldTypes + ModelKeys
|
|
128
|
+
console.log(chalk.green(`\nGenerated ${files.length} file(s)`) +
|
|
129
|
+
chalk.dim(` (${modelCount} model type(s), ${docCount} document(s)${swiftCount > 0 ? `, ${swiftCount} Swift file(s)` : ''})`));
|
|
130
|
+
console.log(chalk.dim(` Types: ${typesDir}`));
|
|
131
|
+
if (docCount > 0) {
|
|
132
|
+
console.log(chalk.dim(` Documents: ${docsDir}`));
|
|
133
|
+
}
|
|
134
|
+
if (config.output.swift) {
|
|
135
|
+
console.log(chalk.dim(` Swift: ${resolve(cwd, config.output.swift)}`));
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
function generateMainIndex() {
|
|
140
|
+
return `/**
|
|
141
|
+
* Generated types and configs
|
|
142
|
+
*
|
|
143
|
+
* @generated by foir — DO NOT EDIT MANUALLY
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
export * from './field-types.js';
|
|
147
|
+
export * from './config.js';
|
|
148
|
+
export * from './models/index.js';
|
|
149
|
+
`;
|
|
150
|
+
}
|