@slingr/cli 0.0.3 → 0.0.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.
Files changed (251) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +490 -319
  3. package/bin/dev.cmd +2 -2
  4. package/bin/dev.js +5 -5
  5. package/bin/run.cmd +2 -2
  6. package/bin/run.js +4 -4
  7. package/bin/slingr +1 -0
  8. package/dist/commands/build.d.ts +20 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +206 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/create-app.d.ts +0 -1
  13. package/dist/commands/create-app.d.ts.map +1 -1
  14. package/dist/commands/create-app.js +38 -57
  15. package/dist/commands/create-app.js.map +1 -1
  16. package/dist/commands/debug.d.ts +28 -0
  17. package/dist/commands/debug.d.ts.map +1 -0
  18. package/dist/commands/debug.js +474 -0
  19. package/dist/commands/debug.js.map +1 -0
  20. package/dist/commands/ds.d.ts +14 -1
  21. package/dist/commands/ds.d.ts.map +1 -1
  22. package/dist/commands/ds.js +450 -121
  23. package/dist/commands/ds.js.map +1 -1
  24. package/dist/commands/gql.d.ts +1 -1
  25. package/dist/commands/gql.d.ts.map +1 -1
  26. package/dist/commands/gql.js +190 -184
  27. package/dist/commands/gql.js.map +1 -1
  28. package/dist/commands/infra/down.d.ts.map +1 -1
  29. package/dist/commands/infra/down.js +8 -7
  30. package/dist/commands/infra/down.js.map +1 -1
  31. package/dist/commands/infra/up.d.ts.map +1 -1
  32. package/dist/commands/infra/up.js +8 -7
  33. package/dist/commands/infra/up.js.map +1 -1
  34. package/dist/commands/infra/update.d.ts +1 -0
  35. package/dist/commands/infra/update.d.ts.map +1 -1
  36. package/dist/commands/infra/update.js +33 -69
  37. package/dist/commands/infra/update.js.map +1 -1
  38. package/dist/commands/run.d.ts +29 -2
  39. package/dist/commands/run.d.ts.map +1 -1
  40. package/dist/commands/run.js +628 -130
  41. package/dist/commands/run.js.map +1 -1
  42. package/dist/commands/setup.d.ts +1 -1
  43. package/dist/commands/setup.d.ts.map +1 -1
  44. package/dist/commands/setup.js +34 -71
  45. package/dist/commands/setup.js.map +1 -1
  46. package/dist/commands/sync-metadata.d.ts +15 -0
  47. package/dist/commands/sync-metadata.d.ts.map +1 -0
  48. package/dist/commands/sync-metadata.js +225 -0
  49. package/dist/commands/sync-metadata.js.map +1 -0
  50. package/dist/commands/users.d.ts +30 -0
  51. package/dist/commands/users.d.ts.map +1 -0
  52. package/dist/commands/users.js +472 -0
  53. package/dist/commands/users.js.map +1 -0
  54. package/dist/commands/views.d.ts +11 -0
  55. package/dist/commands/views.d.ts.map +1 -0
  56. package/dist/commands/views.js +73 -0
  57. package/dist/commands/views.js.map +1 -0
  58. package/dist/projectStructure.d.ts +2 -2
  59. package/dist/projectStructure.d.ts.map +1 -1
  60. package/dist/projectStructure.js +281 -69
  61. package/dist/projectStructure.js.map +1 -1
  62. package/dist/scripts/generate-metadata.d.ts +13 -0
  63. package/dist/scripts/generate-metadata.d.ts.map +1 -0
  64. package/dist/scripts/generate-metadata.js +412 -0
  65. package/dist/scripts/generate-metadata.js.map +1 -0
  66. package/dist/scripts/generate-metadata.ts +498 -0
  67. package/dist/scripts/generate-schema.d.ts +1 -1
  68. package/dist/scripts/generate-schema.js +168 -74
  69. package/dist/scripts/generate-schema.js.map +1 -1
  70. package/dist/scripts/generate-schema.ts +258 -143
  71. package/dist/templates/.env.template +23 -0
  72. package/dist/templates/.firebaserc.template +5 -0
  73. package/dist/templates/.github/copilot-instructions.md.template +652 -17
  74. package/dist/templates/backend/Dockerfile.template +30 -0
  75. package/dist/templates/config/datasource.ts.template +12 -9
  76. package/dist/templates/config/jest.config.ts +30 -30
  77. package/dist/templates/config/jest.setup.ts +1 -1
  78. package/dist/templates/config/tsconfig.json.template +50 -29
  79. package/dist/templates/dataSources/mysql.ts.template +16 -13
  80. package/dist/templates/dataSources/postgres.ts.template +15 -13
  81. package/dist/templates/dataset-generator-script.ts.template +139 -139
  82. package/dist/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  83. package/dist/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  84. package/dist/templates/datasets/mysql-default/App.jsonl.template +4 -4
  85. package/dist/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  86. package/dist/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  87. package/dist/templates/datasets/mysql-default/User.jsonl.template +1 -0
  88. package/dist/templates/datasets/mysql-default/instructions.md.template +1 -0
  89. package/dist/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  90. package/dist/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  91. package/dist/templates/datasets/postgres-default/App.jsonl.template +4 -4
  92. package/dist/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  93. package/dist/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  94. package/dist/templates/datasets/postgres-default/User.jsonl.template +1 -0
  95. package/dist/templates/datasets/postgres-default/instructions.md.template +1 -0
  96. package/dist/templates/docker-compose.prod-test.yml.template +32 -0
  97. package/dist/templates/docker-compose.yml.template +24 -0
  98. package/dist/templates/docs/app-description.md.template +33 -33
  99. package/dist/templates/firebase.json.template +68 -0
  100. package/dist/templates/frontend/.umirc.ts.template +23 -0
  101. package/dist/templates/frontend/package.json.template +45 -0
  102. package/dist/templates/frontend/public/config.json +6 -0
  103. package/dist/templates/frontend/public/logo.svg +6 -0
  104. package/dist/templates/frontend/src/app.tsx.template +44 -0
  105. package/dist/templates/frontend/src/global.less.template +117 -0
  106. package/dist/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  107. package/dist/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  108. package/dist/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  109. package/dist/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  110. package/dist/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  111. package/dist/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  112. package/dist/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  113. package/dist/templates/frontend/tsconfig.json.template +50 -0
  114. package/dist/templates/gql/codegen.yml.template +25 -25
  115. package/dist/templates/gql/index.ts.template +17 -24
  116. package/dist/templates/gql/operations.graphql.template +30 -30
  117. package/dist/templates/ops/README.md.template +1045 -0
  118. package/dist/templates/ops/cloudbuild.yaml.template +161 -0
  119. package/dist/templates/ops/scripts/_utils.js.template +217 -0
  120. package/dist/templates/ops/scripts/deploy.js.template +145 -0
  121. package/dist/templates/ops/scripts/setup-gcp.js.template +330 -0
  122. package/dist/templates/ops/scripts/setup-secrets.js.template +76 -0
  123. package/dist/templates/ops/scripts/test-prod-local.js.template +49 -0
  124. package/dist/templates/package.json.template +50 -38
  125. package/dist/templates/pnpm-workspace.yaml.template +3 -0
  126. package/dist/templates/prompt-analysis.md.template +110 -110
  127. package/dist/templates/prompt-script-generation.md.template +258 -258
  128. package/dist/templates/src/Address.ts.template +28 -31
  129. package/dist/templates/src/App.ts.template +17 -61
  130. package/dist/templates/src/Company.ts.template +41 -47
  131. package/dist/templates/src/Models.test.ts.template +654 -654
  132. package/dist/templates/src/Person.test.ts.template +289 -289
  133. package/dist/templates/src/Person.ts.template +90 -105
  134. package/dist/templates/src/actions/index.ts.template +11 -11
  135. package/dist/templates/src/auth/permissions.ts.template +34 -0
  136. package/dist/templates/src/data/App.ts.template +48 -0
  137. package/dist/templates/src/data/User.ts.template +35 -0
  138. package/dist/templates/src/types/gql.d.ts.template +17 -17
  139. package/dist/templates/vscode/extensions.json +4 -3
  140. package/dist/templates/vscode/settings.json +17 -11
  141. package/dist/templates/workspace-package.json.template +21 -0
  142. package/dist/utils/buildCache.d.ts +12 -0
  143. package/dist/utils/buildCache.d.ts.map +1 -0
  144. package/dist/utils/buildCache.js +102 -0
  145. package/dist/utils/buildCache.js.map +1 -0
  146. package/dist/utils/checkFramework.d.ts +27 -0
  147. package/dist/utils/checkFramework.d.ts.map +1 -0
  148. package/dist/utils/checkFramework.js +104 -0
  149. package/dist/utils/checkFramework.js.map +1 -0
  150. package/dist/utils/datasourceParser.d.ts +11 -0
  151. package/dist/utils/datasourceParser.d.ts.map +1 -1
  152. package/dist/utils/datasourceParser.js +154 -56
  153. package/dist/utils/datasourceParser.js.map +1 -1
  154. package/dist/utils/dockerManager.d.ts +25 -0
  155. package/dist/utils/dockerManager.d.ts.map +1 -0
  156. package/dist/utils/dockerManager.js +281 -0
  157. package/dist/utils/dockerManager.js.map +1 -0
  158. package/dist/utils/infraFileParser.d.ts +26 -0
  159. package/dist/utils/infraFileParser.d.ts.map +1 -0
  160. package/dist/utils/infraFileParser.js +75 -0
  161. package/dist/utils/infraFileParser.js.map +1 -0
  162. package/dist/utils/jsonlLoader.d.ts +91 -12
  163. package/dist/utils/jsonlLoader.d.ts.map +1 -1
  164. package/dist/utils/jsonlLoader.js +674 -63
  165. package/dist/utils/jsonlLoader.js.map +1 -1
  166. package/dist/utils/model-analyzer.d.ts.map +1 -1
  167. package/dist/utils/model-analyzer.js +67 -13
  168. package/dist/utils/model-analyzer.js.map +1 -1
  169. package/dist/utils/userManagement.d.ts +57 -0
  170. package/dist/utils/userManagement.d.ts.map +1 -0
  171. package/dist/utils/userManagement.js +288 -0
  172. package/dist/utils/userManagement.js.map +1 -0
  173. package/dist/utils/viewsGenerator.d.ts +15 -0
  174. package/dist/utils/viewsGenerator.d.ts.map +1 -0
  175. package/dist/utils/viewsGenerator.js +311 -0
  176. package/dist/utils/viewsGenerator.js.map +1 -0
  177. package/oclif.manifest.json +445 -20
  178. package/package.json +29 -26
  179. package/src/templates/.env.template +23 -0
  180. package/src/templates/.firebaserc.template +5 -0
  181. package/src/templates/.github/copilot-instructions.md.template +652 -17
  182. package/src/templates/backend/Dockerfile.template +30 -0
  183. package/src/templates/config/datasource.ts.template +12 -9
  184. package/src/templates/config/jest.config.ts +30 -30
  185. package/src/templates/config/jest.setup.ts +1 -1
  186. package/src/templates/config/tsconfig.json.template +50 -29
  187. package/src/templates/dataSources/mysql.ts.template +16 -13
  188. package/src/templates/dataSources/postgres.ts.template +15 -13
  189. package/src/templates/dataset-generator-script.ts.template +139 -139
  190. package/src/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  191. package/src/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  192. package/src/templates/datasets/mysql-default/App.jsonl.template +4 -4
  193. package/src/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  194. package/src/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  195. package/src/templates/datasets/mysql-default/User.jsonl.template +1 -0
  196. package/src/templates/datasets/mysql-default/instructions.md.template +1 -0
  197. package/src/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  198. package/src/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  199. package/src/templates/datasets/postgres-default/App.jsonl.template +4 -4
  200. package/src/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  201. package/src/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  202. package/src/templates/datasets/postgres-default/User.jsonl.template +1 -0
  203. package/src/templates/datasets/postgres-default/instructions.md.template +1 -0
  204. package/src/templates/docker-compose.prod-test.yml.template +32 -0
  205. package/src/templates/docker-compose.yml.template +24 -0
  206. package/src/templates/docs/app-description.md.template +33 -33
  207. package/src/templates/firebase.json.template +68 -0
  208. package/src/templates/frontend/.umirc.ts.template +23 -0
  209. package/src/templates/frontend/package.json.template +45 -0
  210. package/src/templates/frontend/public/config.json +6 -0
  211. package/src/templates/frontend/public/logo.svg +6 -0
  212. package/src/templates/frontend/src/app.tsx.template +44 -0
  213. package/src/templates/frontend/src/global.less.template +117 -0
  214. package/src/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  215. package/src/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  216. package/src/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  217. package/src/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  218. package/src/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  219. package/src/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  220. package/src/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  221. package/src/templates/frontend/tsconfig.json.template +50 -0
  222. package/src/templates/gql/codegen.yml.template +25 -25
  223. package/src/templates/gql/index.ts.template +17 -24
  224. package/src/templates/gql/operations.graphql.template +30 -30
  225. package/src/templates/ops/README.md.template +1045 -0
  226. package/src/templates/ops/cloudbuild.yaml.template +161 -0
  227. package/src/templates/ops/scripts/_utils.js.template +217 -0
  228. package/src/templates/ops/scripts/deploy.js.template +145 -0
  229. package/src/templates/ops/scripts/setup-gcp.js.template +330 -0
  230. package/src/templates/ops/scripts/setup-secrets.js.template +76 -0
  231. package/src/templates/ops/scripts/test-prod-local.js.template +49 -0
  232. package/src/templates/package.json.template +50 -38
  233. package/src/templates/pnpm-workspace.yaml.template +3 -0
  234. package/src/templates/prompt-analysis.md.template +110 -110
  235. package/src/templates/prompt-script-generation.md.template +258 -258
  236. package/src/templates/src/Address.ts.template +28 -31
  237. package/src/templates/src/App.ts.template +17 -61
  238. package/src/templates/src/Company.ts.template +41 -47
  239. package/src/templates/src/Models.test.ts.template +654 -654
  240. package/src/templates/src/Person.test.ts.template +289 -289
  241. package/src/templates/src/Person.ts.template +90 -105
  242. package/src/templates/src/actions/index.ts.template +11 -11
  243. package/src/templates/src/auth/permissions.ts.template +34 -0
  244. package/src/templates/src/data/App.ts.template +48 -0
  245. package/src/templates/src/data/User.ts.template +35 -0
  246. package/src/templates/src/types/gql.d.ts.template +17 -17
  247. package/src/templates/vscode/extensions.json +4 -3
  248. package/src/templates/vscode/settings.json +17 -11
  249. package/src/templates/workspace-package.json.template +21 -0
  250. package/dist/templates/src/index.ts +0 -66
  251. package/src/templates/src/index.ts +0 -66
@@ -0,0 +1,498 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Script to generate model and action metadata at runtime.
4
+ *
5
+ * This script is executed in the context of a user's Slingr application
6
+ * (copied to backend/ and run via ts-node). It loads models and actions,
7
+ * then uses Reflect.getMetadata() to extract field relationship info and
8
+ * action configuration.
9
+ *
10
+ * Output: frontend/generated/gql/metadata.ts
11
+ */
12
+
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+
16
+ // ============================================================================
17
+ // Types (internal to this script)
18
+ // ============================================================================
19
+
20
+ interface FieldInfoData {
21
+ type: 'scalar' | 'reference' | 'composition' | 'sharedComposition' | 'owner' | 'file';
22
+ target?: string;
23
+ array?: boolean;
24
+ }
25
+
26
+ interface ModelInfoData {
27
+ fields: Record<string, FieldInfoData>;
28
+ persistent: boolean;
29
+ idType?: 'string' | 'int';
30
+ }
31
+
32
+ interface ActionInfoData {
33
+ operation: 'query' | 'mutation';
34
+ type: 'model' | 'global' | 'object';
35
+ paramsRef?: string;
36
+ returnsRef?: string;
37
+ returnsRefs?: string[];
38
+ modelRef?: string;
39
+ label?: string;
40
+ bulk?: boolean;
41
+ // Workflow-specific UI options
42
+ isWorkflow?: boolean;
43
+ blockingExecution?: boolean;
44
+ successMessage?: string;
45
+ errorMessage?: string;
46
+ }
47
+
48
+ // ============================================================================
49
+ // Metadata extraction
50
+ // ============================================================================
51
+
52
+ function resolveTargetClass(proto: object, fieldName: string): Function | null {
53
+ const typeOptions = Reflect.getMetadata('field:type:options', proto, fieldName);
54
+ if (typeOptions?.elementType && typeof typeOptions.elementType === 'function') {
55
+ try {
56
+ const resolved = typeOptions.elementType();
57
+ if (typeof resolved === 'function') {
58
+ return resolved;
59
+ }
60
+ } catch {
61
+ /* fallback */
62
+ }
63
+ }
64
+
65
+ const designType = Reflect.getMetadata('design:type', proto, fieldName);
66
+ if (designType && designType !== Array && designType !== Object && typeof designType === 'function') {
67
+ return designType;
68
+ }
69
+
70
+ return null;
71
+ }
72
+
73
+ function detectIsArray(constructor: Function, proto: object, fieldName: string): boolean {
74
+ const isArrayMeta = Reflect.getMetadata('field:relationship:isArray', constructor, fieldName);
75
+ if (isArrayMeta === true) {
76
+ return true;
77
+ }
78
+
79
+ const arrayFields: string[] = Reflect.getMetadata('array:field:names', constructor) || [];
80
+ if (arrayFields.includes(fieldName)) {
81
+ return true;
82
+ }
83
+
84
+ const designType = Reflect.getMetadata('design:type', proto, fieldName);
85
+ return designType === Array;
86
+ }
87
+
88
+ function detectIdType(modelClass: new (...args: any[]) => any, fieldNames: string[]): 'string' | 'int' | undefined {
89
+ const proto = modelClass.prototype;
90
+
91
+ for (const fieldName of fieldNames) {
92
+ const isPrimaryKey = Reflect.getMetadata('field:primaryKey', proto, fieldName);
93
+ if (isPrimaryKey) {
94
+ const fieldType = Reflect.getMetadata('field:type', proto, fieldName);
95
+ if (fieldType === 'uuid' || fieldType === 'text' || fieldType === 'email') {
96
+ return 'string';
97
+ } else if (fieldType === 'integer') {
98
+ return 'int';
99
+ }
100
+ }
101
+ }
102
+
103
+ return undefined;
104
+ }
105
+
106
+ function extractModelFields(
107
+ modelClass: new (...args: any[]) => any,
108
+ getAllFieldNamesFn: (constructor: Function) => string[]
109
+ ): Record<string, FieldInfoData> {
110
+ const fields: Record<string, FieldInfoData> = {};
111
+ const fieldNames = getAllFieldNamesFn(modelClass);
112
+ const proto = modelClass.prototype;
113
+
114
+ for (const fieldName of fieldNames) {
115
+ const relationshipType: string | undefined = Reflect.getMetadata('field:relationship:type', proto, fieldName);
116
+
117
+ if (!relationshipType) {
118
+ // Scalar field — check if array
119
+ const designType = Reflect.getMetadata('design:type', proto, fieldName);
120
+ const isArray = designType === Array;
121
+ fields[fieldName] = { type: 'scalar', ...(isArray && { array: true }) };
122
+ } else {
123
+ // Relationship field
124
+ const targetClass = resolveTargetClass(proto, fieldName);
125
+ const isArray = detectIsArray(modelClass, proto, fieldName);
126
+
127
+ // Check if target is AppFile or AppFile subclass
128
+ let fieldType: FieldInfoData['type'] = relationshipType as FieldInfoData['type'];
129
+ if (targetClass && relationshipType === 'reference') {
130
+ // Walk prototype chain to check if extends AppFile
131
+ let current = targetClass.prototype;
132
+ while (current) {
133
+ if (current.constructor.name === 'AppFile') {
134
+ fieldType = 'file';
135
+ break;
136
+ }
137
+ current = Object.getPrototypeOf(current);
138
+ }
139
+ }
140
+
141
+ fields[fieldName] = {
142
+ type: fieldType,
143
+ ...(targetClass && { target: targetClass.name }),
144
+ ...(isArray && { array: true }),
145
+ };
146
+ }
147
+ }
148
+
149
+ return fields;
150
+ }
151
+
152
+ // ============================================================================
153
+ // Output generation
154
+ // ============================================================================
155
+
156
+ function generateModelsBlock(models: Record<string, ModelInfoData>): string {
157
+ const entries = Object.keys(models)
158
+ .sort()
159
+ .map(modelName => {
160
+ const modelInfo = models[modelName];
161
+ const fieldEntries = Object.entries(modelInfo.fields)
162
+ .map(([fn, info]) => {
163
+ const props: string[] = [`type: '${info.type}'`];
164
+ if (info.target) {
165
+ props.push(`target: '${info.target}'`);
166
+ }
167
+ if (info.array) {
168
+ props.push(`array: true`);
169
+ }
170
+ return ` ${fn}: { ${props.join(', ')} }`;
171
+ })
172
+ .join(',\n');
173
+
174
+ const modelProps: string[] = [`name: '${modelName}'`, `persistent: ${modelInfo.persistent}`];
175
+
176
+ if (modelInfo.idType) {
177
+ modelProps.push(`idType: '${modelInfo.idType}'`);
178
+ }
179
+
180
+ modelProps.push(`fields: {\n${fieldEntries}\n }`);
181
+ modelProps.push(`actions: {}`);
182
+
183
+ return ` ${modelName}: {\n ${modelProps.join(',\n ')},\n }`;
184
+ })
185
+ .join(',\n');
186
+
187
+ // Add WorkflowType for workflow actions
188
+ const workflowType = ` WorkflowType: {
189
+ name: 'WorkflowType',
190
+ persistent: false,
191
+ fields: {
192
+ id: { type: 'scalar' },
193
+ status: { type: 'scalar' }
194
+ },
195
+ actions: {},
196
+ }`;
197
+
198
+ return entries ? `${entries},\n${workflowType}` : workflowType;
199
+ }
200
+
201
+ function generateActionsBlock(actions: Record<string, ActionInfoData>): string {
202
+ const entries = Object.keys(actions)
203
+ .sort()
204
+ .map(actionName => {
205
+ const info = actions[actionName];
206
+ const props: string[] = [];
207
+ props.push(`operation: '${info.operation}'`);
208
+ props.push(`type: '${info.type}'`);
209
+ if (info.paramsRef) {
210
+ props.push(`params: modelsMetadata['${info.paramsRef}']`);
211
+ }
212
+ if (info.returnsRef) {
213
+ // Check if it's a primitive type (String, Number, Boolean, Date)
214
+ const isPrimitive = ['String', 'Number', 'Boolean', 'Date'].includes(info.returnsRef);
215
+ if (isPrimitive) {
216
+ // For primitive types, don't reference modelsMetadata (it doesn't exist)
217
+ // The frontend doesn't need field expansion for primitives anyway
218
+ props.push(`returns: { name: '${info.returnsRef}', persistent: false, fields: {}, actions: {} }`);
219
+ } else {
220
+ props.push(`returns: modelsMetadata['${info.returnsRef}']`);
221
+ }
222
+ } else if (info.returnsRefs) {
223
+ const refs = info.returnsRefs
224
+ .map(r => {
225
+ const isPrimitive = ['String', 'Number', 'Boolean', 'Date'].includes(r);
226
+ return isPrimitive
227
+ ? `{ name: '${r}', persistent: false, fields: {}, actions: {} }`
228
+ : `modelsMetadata['${r}']`;
229
+ })
230
+ .join(', ');
231
+ props.push(`returns: [${refs}]`);
232
+ }
233
+ if (info.modelRef) {
234
+ props.push(`model: '${info.modelRef}'`);
235
+ }
236
+ if (info.label) {
237
+ props.push(`label: '${info.label}'`);
238
+ }
239
+ if (info.isWorkflow !== undefined) {
240
+ props.push(`isWorkflow: ${info.isWorkflow}`);
241
+ }
242
+ if (info.bulk) {
243
+ props.push(`bulk: ${info.bulk}`);
244
+ }
245
+ if (info.blockingExecution !== undefined) {
246
+ props.push(`blockingExecution: ${info.blockingExecution}`);
247
+ }
248
+ if (info.successMessage) {
249
+ props.push(`successMessage: '${info.successMessage}'`);
250
+ }
251
+ if (info.errorMessage) {
252
+ props.push(`errorMessage: '${info.errorMessage}'`);
253
+ }
254
+ return ` ${actionName}: {\n ${props.join(',\n ')},\n }`;
255
+ })
256
+ .join(',\n');
257
+
258
+ return entries;
259
+ }
260
+
261
+ function generateModelActionWiring(actions: Record<string, ActionInfoData>): string {
262
+ // Group actions by model
263
+ const actionsByModel: Record<string, string[]> = {};
264
+ for (const [actionName, info] of Object.entries(actions)) {
265
+ if (info.modelRef && (info.type === 'model' || info.type === 'object')) {
266
+ if (!actionsByModel[info.modelRef]) {
267
+ actionsByModel[info.modelRef] = [];
268
+ }
269
+ actionsByModel[info.modelRef].push(actionName);
270
+ }
271
+ }
272
+
273
+ const lines: string[] = [];
274
+ for (const [modelName, actionNames] of Object.entries(actionsByModel).sort(([a], [b]) => a.localeCompare(b))) {
275
+ const actionEntries = actionNames
276
+ .sort()
277
+ .map(name => ` ${name}: actionsMetadata['${name}']`)
278
+ .join(',\n');
279
+ lines.push(`modelsMetadata['${modelName}'].actions = {\n${actionEntries},\n};`);
280
+ }
281
+
282
+ return lines.join('\n\n');
283
+ }
284
+
285
+ function generateOutputFile(models: Record<string, ModelInfoData>, actions: Record<string, ActionInfoData>): string {
286
+ const modelsBlock = generateModelsBlock(models);
287
+ const actionsBlock = generateActionsBlock(actions);
288
+ const wiringBlock = generateModelActionWiring(actions);
289
+
290
+ return `/**
291
+ * Models & Actions Metadata - Auto-generated by Slingr CLI
292
+ *
293
+ * This file provides model field metadata and action metadata for use by
294
+ * the QueryBuilder, OperationBuilder, and other framework components.
295
+ *
296
+ * DO NOT EDIT THIS FILE MANUALLY - regenerate with: slingr gql generate-sdk
297
+ */
298
+
299
+ import type {
300
+ ModelsMetadata,
301
+ ActionsMetadata,
302
+ } from '@slingr/framework-frontend';
303
+
304
+ // Phase 1: Models (actions populated in phase 3)
305
+ export const modelsMetadata: ModelsMetadata = {
306
+ ${modelsBlock}
307
+ };
308
+
309
+ // Phase 2: Actions (reference models directly)
310
+ export const actionsMetadata = {
311
+ ${actionsBlock}
312
+ } satisfies ActionsMetadata;
313
+
314
+ // Augment global registry so \`ActionName\` (from @slingr/framework-frontend) resolves
315
+ // to a specific union of action name literals rather than plain \`string\`.
316
+ declare global {
317
+ interface SlingrActionRegistry extends Record<keyof typeof actionsMetadata, true> {}
318
+ }
319
+
320
+ // Phase 3: Wire model → action references (model + object actions only)
321
+ ${wiringBlock}
322
+ `;
323
+ }
324
+
325
+ // ============================================================================
326
+ // Main
327
+ // ============================================================================
328
+
329
+ export async function generateMetadataFile() {
330
+ try {
331
+ console.log('🔄 Generating metadata from model/action decorators...');
332
+
333
+ // Dynamic import to avoid compile-time dependency on @slingr/framework-backend
334
+ // This script runs in the user's backend context where @slingr/framework-backend is installed
335
+ // @ts-ignore
336
+ const slingrFramework: any = await import('@slingr/framework-backend');
337
+ const { getAllModels, getAllActions, getAllFieldNames } = slingrFramework;
338
+
339
+ // Extract model metadata
340
+ const allModels = getAllModels();
341
+ const modelsMetadata: Record<string, ModelInfoData> = {};
342
+
343
+ for (const modelClass of allModels) {
344
+ const fields = extractModelFields(modelClass, getAllFieldNames);
345
+ const dataSource = Reflect.getMetadata('model:dataSource', modelClass);
346
+ const isPersistent = !!dataSource;
347
+
348
+ if (Object.keys(fields).length > 0) {
349
+ const metadata: ModelInfoData = {
350
+ fields,
351
+ persistent: isPersistent,
352
+ };
353
+
354
+ if (isPersistent) {
355
+ const fieldNames = getAllFieldNames(modelClass);
356
+ const idType = detectIdType(modelClass, fieldNames);
357
+ if (idType) {
358
+ metadata.idType = idType;
359
+ }
360
+ }
361
+
362
+ modelsMetadata[modelClass.name] = metadata;
363
+ }
364
+ }
365
+
366
+ // Extract action metadata (only api: 'gql')
367
+ const allActions = getAllActions();
368
+ const actionsMetadata: Record<string, ActionInfoData> = {};
369
+
370
+ for (const { actionClass, options, actionType } of allActions) {
371
+ if (options.api !== 'gql') {
372
+ continue;
373
+ }
374
+
375
+ // Ensure params model is in modelsMetadata
376
+ if (options.params && !modelsMetadata[options.params.name]) {
377
+ const fields = extractModelFields(options.params, getAllFieldNames);
378
+ const dataSource = Reflect.getMetadata('model:dataSource', options.params);
379
+ const isPersistent = !!dataSource;
380
+
381
+ if (Object.keys(fields).length > 0) {
382
+ const metadata: ModelInfoData = {
383
+ fields,
384
+ persistent: isPersistent,
385
+ };
386
+
387
+ if (isPersistent) {
388
+ const fieldNames = getAllFieldNames(options.params);
389
+ const idType = detectIdType(options.params, fieldNames);
390
+ if (idType) {
391
+ metadata.idType = idType;
392
+ }
393
+ }
394
+
395
+ modelsMetadata[options.params.name] = metadata;
396
+ }
397
+ }
398
+
399
+ // Ensure returns model(s) are in modelsMetadata
400
+ const returnsArr = Array.isArray(options.returns) ? options.returns : options.returns ? [options.returns] : [];
401
+ for (const ret of returnsArr) {
402
+ if (!modelsMetadata[ret.name]) {
403
+ const fields = extractModelFields(ret, getAllFieldNames);
404
+ const dataSource = Reflect.getMetadata('model:dataSource', ret);
405
+ const isPersistent = !!dataSource;
406
+
407
+ if (Object.keys(fields).length > 0) {
408
+ const metadata: ModelInfoData = {
409
+ fields,
410
+ persistent: isPersistent,
411
+ };
412
+
413
+ if (isPersistent) {
414
+ const fieldNames = getAllFieldNames(ret);
415
+ const idType = detectIdType(ret, fieldNames);
416
+ if (idType) {
417
+ metadata.idType = idType;
418
+ }
419
+ }
420
+
421
+ modelsMetadata[ret.name] = metadata;
422
+ }
423
+ }
424
+ }
425
+
426
+ // Extract UI options (handle both single object and contextual array)
427
+ const uiOptions = Array.isArray(options.ui) ? options.ui[0] : options.ui;
428
+
429
+ const isWorkflow = options.workflow === true;
430
+ const isBulk = options.bulk === true || uiOptions?.bulk === true;
431
+ // Default blockingExecution to true for workflow and bulk actions — both block the UI by design
432
+ const isBlockingByDefault = isWorkflow || isBulk;
433
+ const blockingExecution = uiOptions?.blockingExecution ?? isBlockingByDefault;
434
+
435
+ const info: ActionInfoData = {
436
+ operation: options.type === 'read' ? 'query' : 'mutation',
437
+ type: actionType,
438
+ label: uiOptions?.label,
439
+ isWorkflow,
440
+ blockingExecution,
441
+ successMessage: uiOptions?.successMessage,
442
+ errorMessage: uiOptions?.errorMessage,
443
+ };
444
+ if (isBulk) {
445
+ info.bulk = true;
446
+ }
447
+
448
+ if (options.params) {
449
+ info.paramsRef = options.params.name;
450
+ }
451
+
452
+ if (options.returns) {
453
+ // For workflows, the actual GraphQL return type is WorkflowType (not the declared type)
454
+ if (options.workflow === true) {
455
+ info.returnsRef = 'WorkflowType';
456
+ } else if (Array.isArray(options.returns)) {
457
+ info.returnsRefs = options.returns.map((cls: Function) => cls.name);
458
+ } else {
459
+ info.returnsRef = options.returns.name;
460
+ }
461
+ }
462
+
463
+ if (options.model) {
464
+ info.modelRef = options.model.name;
465
+ }
466
+
467
+ actionsMetadata[actionClass.name] = info;
468
+ }
469
+
470
+ // Generate output
471
+ const outputContent = generateOutputFile(modelsMetadata, actionsMetadata);
472
+
473
+ // Write to frontend/generated/gql/metadata.ts
474
+ const frontendPath = path.resolve(process.cwd(), '..', 'frontend');
475
+ const outputDir = path.join(frontendPath, 'generated', 'gql');
476
+ const outputPath = path.join(outputDir, 'metadata.ts');
477
+
478
+ if (!fs.existsSync(outputDir)) {
479
+ fs.mkdirSync(outputDir, { recursive: true });
480
+ }
481
+
482
+ fs.writeFileSync(outputPath, outputContent, 'utf8');
483
+
484
+ const modelCount = Object.keys(modelsMetadata).length;
485
+ const actionCount = Object.keys(actionsMetadata).length;
486
+ const fieldCount = Object.values(modelsMetadata).reduce(
487
+ (sum, modelInfo) => sum + Object.keys(modelInfo.fields).length,
488
+ 0
489
+ );
490
+
491
+ console.log(` Found ${modelCount} models with ${fieldCount} fields and ${actionCount} actions`);
492
+ console.log(`✅ Metadata generated: ${outputPath}`);
493
+ } catch (error: any) {
494
+ console.error(`❌ Failed to generate metadata: ${error.message}`);
495
+ console.error(error);
496
+ process.exit(1);
497
+ }
498
+ }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Script to generate GraphQL schema from slingr-framework
3
+ * Script to generate GraphQL schema from @slingr/framework-backend
4
4
  * This script is executed in the context of a user's Slingr application
5
5
  *
6
6
  * By default, the script generates the schema and exits immediately.