@objectstack/cli 4.0.4 → 4.1.0

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 (269) hide show
  1. package/README.md +44 -25
  2. package/dist/commands/build.d.ts +5 -0
  3. package/dist/commands/build.d.ts.map +1 -0
  4. package/dist/commands/build.js +6 -0
  5. package/dist/commands/build.js.map +1 -0
  6. package/dist/commands/cloud/login.d.ts +16 -0
  7. package/dist/commands/cloud/login.d.ts.map +1 -0
  8. package/dist/commands/cloud/login.js +166 -0
  9. package/dist/commands/cloud/login.js.map +1 -0
  10. package/dist/commands/cloud/logout.d.ts +15 -0
  11. package/dist/commands/cloud/logout.d.ts.map +1 -0
  12. package/dist/commands/cloud/logout.js +51 -0
  13. package/dist/commands/cloud/logout.js.map +1 -0
  14. package/dist/commands/cloud/whoami.d.ts +15 -0
  15. package/dist/commands/cloud/whoami.d.ts.map +1 -0
  16. package/dist/commands/cloud/whoami.js +81 -0
  17. package/dist/commands/cloud/whoami.js.map +1 -0
  18. package/dist/commands/compile.d.ts +3 -0
  19. package/dist/commands/compile.d.ts.map +1 -1
  20. package/dist/commands/compile.js +128 -6
  21. package/dist/commands/compile.js.map +1 -1
  22. package/dist/commands/create.js +1 -1
  23. package/dist/commands/data/create.js +2 -2
  24. package/dist/commands/data/create.js.map +1 -1
  25. package/dist/commands/data/delete.js +2 -2
  26. package/dist/commands/data/delete.js.map +1 -1
  27. package/dist/commands/data/get.js +2 -2
  28. package/dist/commands/data/get.js.map +1 -1
  29. package/dist/commands/data/query.js +2 -2
  30. package/dist/commands/data/query.js.map +1 -1
  31. package/dist/commands/data/update.js +2 -2
  32. package/dist/commands/data/update.js.map +1 -1
  33. package/dist/commands/dev.d.ts +9 -0
  34. package/dist/commands/dev.d.ts.map +1 -1
  35. package/dist/commands/dev.js +116 -22
  36. package/dist/commands/dev.js.map +1 -1
  37. package/dist/commands/generate.js +9 -9
  38. package/dist/commands/generate.js.map +1 -1
  39. package/dist/commands/i18n/check.d.ts +18 -0
  40. package/dist/commands/i18n/check.d.ts.map +1 -0
  41. package/dist/commands/i18n/check.js +153 -0
  42. package/dist/commands/i18n/check.js.map +1 -0
  43. package/dist/commands/init.js +2 -2
  44. package/dist/commands/lint.d.ts +3 -0
  45. package/dist/commands/lint.d.ts.map +1 -1
  46. package/dist/commands/lint.js +24 -0
  47. package/dist/commands/lint.js.map +1 -1
  48. package/dist/commands/login.d.ts +17 -0
  49. package/dist/commands/login.d.ts.map +1 -0
  50. package/dist/commands/login.js +313 -0
  51. package/dist/commands/login.js.map +1 -0
  52. package/dist/commands/logout.d.ts.map +1 -0
  53. package/dist/commands/{auth/logout.js → logout.js} +14 -2
  54. package/dist/commands/logout.js.map +1 -0
  55. package/dist/commands/meta/delete.js +2 -2
  56. package/dist/commands/meta/delete.js.map +1 -1
  57. package/dist/commands/meta/get.js +2 -2
  58. package/dist/commands/meta/get.js.map +1 -1
  59. package/dist/commands/meta/list.js +2 -2
  60. package/dist/commands/meta/list.js.map +1 -1
  61. package/dist/commands/meta/register.js +2 -2
  62. package/dist/commands/meta/register.js.map +1 -1
  63. package/dist/commands/package/publish.d.ts +32 -0
  64. package/dist/commands/package/publish.d.ts.map +1 -0
  65. package/dist/commands/package/publish.js +324 -0
  66. package/dist/commands/package/publish.js.map +1 -0
  67. package/dist/commands/projects/bind.d.ts +30 -0
  68. package/dist/commands/projects/bind.d.ts.map +1 -0
  69. package/dist/commands/projects/bind.js +132 -0
  70. package/dist/commands/projects/bind.js.map +1 -0
  71. package/dist/commands/projects/create.d.ts +28 -0
  72. package/dist/commands/projects/create.d.ts.map +1 -0
  73. package/dist/commands/projects/create.js +120 -0
  74. package/dist/commands/projects/create.js.map +1 -0
  75. package/dist/commands/projects/list.d.ts +21 -0
  76. package/dist/commands/projects/list.d.ts.map +1 -0
  77. package/dist/commands/projects/list.js +79 -0
  78. package/dist/commands/projects/list.js.map +1 -0
  79. package/dist/commands/projects/projects.test.d.ts +2 -0
  80. package/dist/commands/projects/projects.test.d.ts.map +1 -0
  81. package/dist/commands/projects/projects.test.js +56 -0
  82. package/dist/commands/projects/projects.test.js.map +1 -0
  83. package/dist/commands/projects/show.d.ts +21 -0
  84. package/dist/commands/projects/show.d.ts.map +1 -0
  85. package/dist/commands/projects/show.js +72 -0
  86. package/dist/commands/projects/show.js.map +1 -0
  87. package/dist/commands/projects/switch.d.ts +24 -0
  88. package/dist/commands/projects/switch.d.ts.map +1 -0
  89. package/dist/commands/projects/switch.js +64 -0
  90. package/dist/commands/projects/switch.js.map +1 -0
  91. package/dist/commands/publish.d.ts +17 -0
  92. package/dist/commands/publish.d.ts.map +1 -0
  93. package/dist/commands/publish.js +135 -0
  94. package/dist/commands/publish.js.map +1 -0
  95. package/dist/commands/{auth/login.d.ts → register.d.ts} +3 -2
  96. package/dist/commands/register.d.ts.map +1 -0
  97. package/dist/commands/{auth/login.js → register.js} +44 -61
  98. package/dist/commands/register.js.map +1 -0
  99. package/dist/commands/rollback.d.ts +13 -0
  100. package/dist/commands/rollback.d.ts.map +1 -0
  101. package/dist/commands/rollback.js +77 -0
  102. package/dist/commands/rollback.js.map +1 -0
  103. package/dist/commands/serve.d.ts +22 -0
  104. package/dist/commands/serve.d.ts.map +1 -1
  105. package/dist/commands/serve.js +1173 -58
  106. package/dist/commands/serve.js.map +1 -1
  107. package/dist/commands/start.d.ts +18 -0
  108. package/dist/commands/start.d.ts.map +1 -0
  109. package/dist/commands/start.js +112 -0
  110. package/dist/commands/start.js.map +1 -0
  111. package/dist/commands/whoami.d.ts.map +1 -0
  112. package/dist/commands/{auth/whoami.js → whoami.js} +5 -5
  113. package/dist/commands/whoami.js.map +1 -0
  114. package/dist/index.d.ts +11 -4
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +14 -5
  117. package/dist/index.js.map +1 -1
  118. package/dist/utils/account.d.ts +31 -0
  119. package/dist/utils/account.d.ts.map +1 -0
  120. package/dist/utils/account.js +154 -0
  121. package/dist/utils/account.js.map +1 -0
  122. package/dist/utils/api-client.d.ts +10 -4
  123. package/dist/utils/api-client.d.ts.map +1 -1
  124. package/dist/utils/api-client.js +13 -7
  125. package/dist/utils/api-client.js.map +1 -1
  126. package/dist/utils/auth-config.d.ts +6 -0
  127. package/dist/utils/auth-config.d.ts.map +1 -1
  128. package/dist/utils/auth-config.js.map +1 -1
  129. package/dist/utils/auth-flows.d.ts +31 -0
  130. package/dist/utils/auth-flows.d.ts.map +1 -0
  131. package/dist/utils/auth-flows.js +151 -0
  132. package/dist/utils/auth-flows.js.map +1 -0
  133. package/dist/utils/build-runtime.d.ts +45 -0
  134. package/dist/utils/build-runtime.d.ts.map +1 -0
  135. package/dist/utils/build-runtime.js +154 -0
  136. package/dist/utils/build-runtime.js.map +1 -0
  137. package/dist/utils/cloud-config.d.ts +24 -0
  138. package/dist/utils/cloud-config.d.ts.map +1 -0
  139. package/dist/utils/cloud-config.js +75 -0
  140. package/dist/utils/cloud-config.js.map +1 -0
  141. package/dist/utils/config.d.ts.map +1 -1
  142. package/dist/utils/config.js +17 -2
  143. package/dist/utils/config.js.map +1 -1
  144. package/dist/utils/console.d.ts +33 -0
  145. package/dist/utils/console.d.ts.map +1 -0
  146. package/dist/utils/console.js +172 -0
  147. package/dist/utils/console.js.map +1 -0
  148. package/dist/utils/extract-hook-body.d.ts +13 -0
  149. package/dist/utils/extract-hook-body.d.ts.map +1 -0
  150. package/dist/utils/extract-hook-body.js +175 -0
  151. package/dist/utils/extract-hook-body.js.map +1 -0
  152. package/dist/utils/format.d.ts +8 -0
  153. package/dist/utils/format.d.ts.map +1 -1
  154. package/dist/utils/format.js +15 -2
  155. package/dist/utils/format.js.map +1 -1
  156. package/dist/utils/i18n-coverage.d.ts +61 -0
  157. package/dist/utils/i18n-coverage.d.ts.map +1 -0
  158. package/dist/utils/i18n-coverage.js +176 -0
  159. package/dist/utils/i18n-coverage.js.map +1 -0
  160. package/dist/utils/lower-callables.d.ts +17 -0
  161. package/dist/utils/lower-callables.d.ts.map +1 -0
  162. package/dist/utils/lower-callables.js +181 -0
  163. package/dist/utils/lower-callables.js.map +1 -0
  164. package/dist/utils/plugin-detection.d.ts +1 -0
  165. package/dist/utils/plugin-detection.d.ts.map +1 -1
  166. package/dist/utils/plugin-detection.js +41 -0
  167. package/dist/utils/plugin-detection.js.map +1 -1
  168. package/dist/utils/studio.d.ts +1 -0
  169. package/dist/utils/studio.d.ts.map +1 -1
  170. package/dist/utils/studio.js +24 -9
  171. package/dist/utils/studio.js.map +1 -1
  172. package/package.json +60 -22
  173. package/.turbo/turbo-build.log +0 -4
  174. package/CHANGELOG.md +0 -821
  175. package/bin/run-dev.js +0 -5
  176. package/dist/commands/auth/login.d.ts.map +0 -1
  177. package/dist/commands/auth/login.js.map +0 -1
  178. package/dist/commands/auth/logout.d.ts.map +0 -1
  179. package/dist/commands/auth/logout.js.map +0 -1
  180. package/dist/commands/auth/whoami.d.ts.map +0 -1
  181. package/dist/commands/auth/whoami.js.map +0 -1
  182. package/dist/commands/codemod/v2-to-v3.d.ts +0 -10
  183. package/dist/commands/codemod/v2-to-v3.d.ts.map +0 -1
  184. package/dist/commands/codemod/v2-to-v3.js +0 -145
  185. package/dist/commands/codemod/v2-to-v3.js.map +0 -1
  186. package/dist/commands/plugin/add.d.ts +0 -22
  187. package/dist/commands/plugin/add.d.ts.map +0 -1
  188. package/dist/commands/plugin/add.js +0 -93
  189. package/dist/commands/plugin/add.js.map +0 -1
  190. package/dist/commands/plugin/build.d.ts +0 -29
  191. package/dist/commands/plugin/build.d.ts.map +0 -1
  192. package/dist/commands/plugin/build.js +0 -170
  193. package/dist/commands/plugin/build.js.map +0 -1
  194. package/dist/commands/plugin/info.d.ts +0 -10
  195. package/dist/commands/plugin/info.d.ts.map +0 -1
  196. package/dist/commands/plugin/info.js +0 -65
  197. package/dist/commands/plugin/info.js.map +0 -1
  198. package/dist/commands/plugin/list.d.ts +0 -13
  199. package/dist/commands/plugin/list.d.ts.map +0 -1
  200. package/dist/commands/plugin/list.js +0 -78
  201. package/dist/commands/plugin/list.js.map +0 -1
  202. package/dist/commands/plugin/publish.d.ts +0 -27
  203. package/dist/commands/plugin/publish.d.ts.map +0 -1
  204. package/dist/commands/plugin/publish.js +0 -152
  205. package/dist/commands/plugin/publish.js.map +0 -1
  206. package/dist/commands/plugin/remove.d.ts +0 -20
  207. package/dist/commands/plugin/remove.d.ts.map +0 -1
  208. package/dist/commands/plugin/remove.js +0 -79
  209. package/dist/commands/plugin/remove.js.map +0 -1
  210. package/dist/commands/plugin/validate.d.ts +0 -23
  211. package/dist/commands/plugin/validate.d.ts.map +0 -1
  212. package/dist/commands/plugin/validate.js +0 -251
  213. package/dist/commands/plugin/validate.js.map +0 -1
  214. package/src/bin.ts +0 -13
  215. package/src/commands/auth/login.ts +0 -188
  216. package/src/commands/auth/logout.ts +0 -51
  217. package/src/commands/auth/whoami.ts +0 -85
  218. package/src/commands/codemod/v2-to-v3.ts +0 -171
  219. package/src/commands/compile.ts +0 -114
  220. package/src/commands/create.ts +0 -281
  221. package/src/commands/data/create.ts +0 -110
  222. package/src/commands/data/delete.ts +0 -84
  223. package/src/commands/data/get.ts +0 -84
  224. package/src/commands/data/query.ts +0 -127
  225. package/src/commands/data/update.ts +0 -114
  226. package/src/commands/dev.ts +0 -83
  227. package/src/commands/diff.ts +0 -294
  228. package/src/commands/doctor.ts +0 -572
  229. package/src/commands/explain.ts +0 -412
  230. package/src/commands/generate.ts +0 -924
  231. package/src/commands/info.ts +0 -124
  232. package/src/commands/init.ts +0 -327
  233. package/src/commands/lint.ts +0 -315
  234. package/src/commands/meta/delete.ts +0 -79
  235. package/src/commands/meta/get.ts +0 -73
  236. package/src/commands/meta/list.ts +0 -105
  237. package/src/commands/meta/register.ts +0 -97
  238. package/src/commands/plugin/add.ts +0 -112
  239. package/src/commands/plugin/build.ts +0 -193
  240. package/src/commands/plugin/info.ts +0 -79
  241. package/src/commands/plugin/list.ts +0 -93
  242. package/src/commands/plugin/publish.ts +0 -176
  243. package/src/commands/plugin/remove.ts +0 -97
  244. package/src/commands/plugin/validate.ts +0 -268
  245. package/src/commands/serve.ts +0 -411
  246. package/src/commands/studio.ts +0 -52
  247. package/src/commands/test.ts +0 -135
  248. package/src/commands/validate.ts +0 -143
  249. package/src/index.ts +0 -22
  250. package/src/utils/api-client.ts +0 -88
  251. package/src/utils/auth-config.ts +0 -107
  252. package/src/utils/config.ts +0 -80
  253. package/src/utils/format.ts +0 -267
  254. package/src/utils/output-formatter.ts +0 -91
  255. package/src/utils/plugin-detection.ts +0 -16
  256. package/src/utils/plugin-helpers.ts +0 -37
  257. package/src/utils/studio.ts +0 -350
  258. package/test/commands.test.ts +0 -128
  259. package/test/create.test.ts +0 -25
  260. package/test/plugin-commands.test.ts +0 -44
  261. package/test/plugin.test.ts +0 -169
  262. package/test/remote-api-commands.test.ts +0 -188
  263. package/test/remote-api-utils.test.ts +0 -196
  264. package/test/serve-host-config.test.ts +0 -77
  265. package/tsconfig.build.json +0 -20
  266. package/tsconfig.json +0 -25
  267. package/tsup.config.ts +0 -23
  268. /package/dist/commands/{auth/logout.d.ts → logout.d.ts} +0 -0
  269. /package/dist/commands/{auth/whoami.d.ts → whoami.d.ts} +0 -0
@@ -1,924 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { Args, Command, Flags } from '@oclif/core';
4
- import chalk from 'chalk';
5
- import fs from 'fs';
6
- import path from 'path';
7
- import { printHeader, printSuccess, printError, printInfo, printStep, createTimer } from '../utils/format.js';
8
-
9
- // ─── Metadata Type Templates ────────────────────────────────────────
10
-
11
- const GENERATORS: Record<string, {
12
- description: string;
13
- defaultDir: string;
14
- generate: (name: string) => string;
15
- }> = {
16
- object: {
17
- description: 'Business data object',
18
- defaultDir: 'src/objects',
19
- generate: (name: string) => `import { Data } from '@objectstack/spec';
20
-
21
- /**
22
- * ${toTitleCase(name)} Object
23
- */
24
- const ${toCamelCase(name)}: Data.Object = {
25
- name: '${toSnakeCase(name)}',
26
- label: '${toTitleCase(name)}',
27
- pluralLabel: '${toTitleCase(name)}s',
28
- ownership: 'own',
29
- fields: {
30
- name: {
31
- type: 'text',
32
- label: 'Name',
33
- required: true,
34
- maxLength: 255,
35
- },
36
- description: {
37
- type: 'textarea',
38
- label: 'Description',
39
- },
40
- },
41
- };
42
-
43
- export default ${toCamelCase(name)};
44
- `,
45
- },
46
-
47
- view: {
48
- description: 'List or form view',
49
- defaultDir: 'src/views',
50
- generate: (name: string) => `import { UI } from '@objectstack/spec';
51
-
52
- /**
53
- * ${toTitleCase(name)} List View
54
- */
55
- const ${toCamelCase(name)}ListView: UI.View = {
56
- name: '${toSnakeCase(name)}_list',
57
- label: '${toTitleCase(name)} List',
58
- type: 'list',
59
- objectName: '${toSnakeCase(name)}',
60
- list: {
61
- type: 'grid',
62
- columns: [
63
- { field: 'name', width: 200 },
64
- ],
65
- defaultSort: { field: 'name', direction: 'asc' },
66
- pageSize: 25,
67
- },
68
- };
69
-
70
- export default ${toCamelCase(name)}ListView;
71
- `,
72
- },
73
-
74
- action: {
75
- description: 'Button or batch action',
76
- defaultDir: 'src/actions',
77
- generate: (name: string) => `import { UI } from '@objectstack/spec';
78
-
79
- /**
80
- * ${toTitleCase(name)} Action
81
- */
82
- const ${toCamelCase(name)}Action: UI.Action = {
83
- name: '${toSnakeCase(name)}',
84
- label: '${toTitleCase(name)}',
85
- type: 'custom',
86
- objectName: '${toSnakeCase(name)}',
87
- handler: {
88
- type: 'flow',
89
- target: '${toSnakeCase(name)}_flow',
90
- },
91
- };
92
-
93
- export default ${toCamelCase(name)}Action;
94
- `,
95
- },
96
-
97
- flow: {
98
- description: 'Automation flow',
99
- defaultDir: 'src/flows',
100
- generate: (name: string) => `import { Automation } from '@objectstack/spec';
101
-
102
- /**
103
- * ${toTitleCase(name)} Flow
104
- */
105
- const ${toCamelCase(name)}Flow: Automation.Flow = {
106
- name: '${toSnakeCase(name)}_flow',
107
- label: '${toTitleCase(name)} Flow',
108
- type: 'autolaunched',
109
- status: 'draft',
110
- trigger: {
111
- type: 'record_change',
112
- object: '${toSnakeCase(name)}',
113
- events: ['after_insert', 'after_update'],
114
- },
115
- nodes: [
116
- {
117
- id: 'start',
118
- type: 'start',
119
- name: 'Start',
120
- next: 'end',
121
- },
122
- ],
123
- };
124
-
125
- export default ${toCamelCase(name)}Flow;
126
- `,
127
- },
128
-
129
- agent: {
130
- description: 'AI agent',
131
- defaultDir: 'src/agents',
132
- generate: (name: string) => `import { AI } from '@objectstack/spec';
133
-
134
- /**
135
- * ${toTitleCase(name)} Agent
136
- */
137
- const ${toCamelCase(name)}Agent: AI.Agent = {
138
- name: '${toSnakeCase(name)}_agent',
139
- label: '${toTitleCase(name)} Agent',
140
- role: '${toTitleCase(name)} assistant',
141
- instructions: 'You are a helpful ${toTitleCase(name).toLowerCase()} assistant.',
142
- model: {
143
- provider: 'openai',
144
- model: 'gpt-4o',
145
- },
146
- tools: [],
147
- };
148
-
149
- export default ${toCamelCase(name)}Agent;
150
- `,
151
- },
152
-
153
- dashboard: {
154
- description: 'Analytics dashboard',
155
- defaultDir: 'src/dashboards',
156
- generate: (name: string) => `import { UI } from '@objectstack/spec';
157
-
158
- /**
159
- * ${toTitleCase(name)} Dashboard
160
- */
161
- const ${toCamelCase(name)}Dashboard: UI.Dashboard = {
162
- name: '${toSnakeCase(name)}_dashboard',
163
- label: '${toTitleCase(name)} Dashboard',
164
- widgets: [],
165
- };
166
-
167
- export default ${toCamelCase(name)}Dashboard;
168
- `,
169
- },
170
-
171
- app: {
172
- description: 'Application navigation',
173
- defaultDir: 'src/apps',
174
- generate: (name: string) => `import { UI } from '@objectstack/spec';
175
-
176
- /**
177
- * ${toTitleCase(name)} App
178
- */
179
- const ${toCamelCase(name)}App: UI.App = {
180
- name: '${toSnakeCase(name)}_app',
181
- label: '${toTitleCase(name)}',
182
- navigation: {
183
- type: 'sidebar',
184
- items: [],
185
- },
186
- };
187
-
188
- export default ${toCamelCase(name)}App;
189
- `,
190
- },
191
- };
192
-
193
- // ─── Helpers ────────────────────────────────────────────────────────
194
-
195
- function toCamelCase(str: string): string {
196
- return str.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
197
- }
198
-
199
- function toTitleCase(str: string): string {
200
- return str.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
201
- }
202
-
203
- function toSnakeCase(str: string): string {
204
- return str.replace(/[-]/g, '_').replace(/[A-Z]/g, c => `_${c.toLowerCase()}`).replace(/^_/, '');
205
- }
206
-
207
- // ─── Field Type Mapping ─────────────────────────────────────────────
208
-
209
- const FIELD_TYPE_MAP: Record<string, string> = {
210
- text: 'string',
211
- textarea: 'string',
212
- richtext: 'string',
213
- html: 'string',
214
- markdown: 'string',
215
- number: 'number',
216
- integer: 'number',
217
- currency: 'number',
218
- percent: 'number',
219
- boolean: 'boolean',
220
- date: 'string',
221
- datetime: 'string',
222
- time: 'string',
223
- email: 'string',
224
- phone: 'string',
225
- url: 'string',
226
- select: 'string',
227
- multiselect: 'string[]',
228
- lookup: 'string',
229
- master_detail: 'string',
230
- formula: 'unknown',
231
- autonumber: 'string',
232
- json: 'Record<string, unknown>',
233
- file: 'string',
234
- image: 'string',
235
- password: 'string',
236
- slug: 'string',
237
- uuid: 'string',
238
- ip_address: 'string',
239
- color: 'string',
240
- rating: 'number',
241
- geo_point: '{ lat: number; lng: number }',
242
- vector: 'number[]',
243
- encrypted: 'string',
244
- };
245
-
246
- function fieldTypeToTs(fieldType: string, multiple?: boolean): string {
247
- const base = FIELD_TYPE_MAP[fieldType] || 'unknown';
248
- return multiple ? `${base}[]` : base;
249
- }
250
-
251
- function generateTypesFromConfig(config: Record<string, unknown>): string {
252
- const lines: string[] = [
253
- '// Auto-generated by ObjectStack CLI — do not edit manually',
254
- `// Generated at ${new Date().toISOString()}`,
255
- '',
256
- "import type { Data } from '@objectstack/spec';",
257
- '',
258
- ];
259
-
260
- // Extract objects from config (supports both top-level and nested)
261
- const objects: Record<string, unknown>[] = [];
262
- const rawObjects = (config as any).objects ?? (config as any).data?.objects ?? {};
263
-
264
- if (Array.isArray(rawObjects)) {
265
- objects.push(...rawObjects);
266
- } else if (typeof rawObjects === 'object') {
267
- for (const val of Object.values(rawObjects)) {
268
- if (val && typeof val === 'object') objects.push(val as Record<string, unknown>);
269
- }
270
- }
271
-
272
- if (objects.length === 0) {
273
- lines.push('// No objects found in configuration');
274
- return lines.join('\n') + '\n';
275
- }
276
-
277
- for (const obj of objects) {
278
- const name = String(obj.name || 'unknown');
279
- const typeName = name
280
- .split('_')
281
- .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
282
- .join('');
283
- const fields = (obj.fields ?? {}) as Record<string, Record<string, unknown>>;
284
-
285
- lines.push(`/** ${String(obj.label || typeName)} record type */`);
286
- lines.push(`export interface ${typeName}Record {`);
287
- lines.push(' id: string;');
288
-
289
- for (const [fieldName, fieldDef] of Object.entries(fields)) {
290
- const fType = String(fieldDef.type || 'text');
291
- const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
292
- const required = fieldDef.required ? '' : '?';
293
- if (fieldDef.label) {
294
- lines.push(` /** ${fieldDef.label} */`);
295
- }
296
- lines.push(` ${fieldName}${required}: ${tsType};`);
297
- }
298
-
299
- lines.push('}');
300
- lines.push('');
301
- }
302
-
303
- return lines.join('\n') + '\n';
304
- }
305
-
306
- // ─── Command ────────────────────────────────────────────────────────
307
-
308
- async function runMetadataGeneration(type: string, name: string, flags: { dir?: string; dryRun?: boolean }): Promise<void> {
309
- printHeader('Generate');
310
-
311
- const generator = GENERATORS[type];
312
- if (!generator) {
313
- printError(`Unknown type: ${type}`);
314
- console.log('');
315
- console.log(chalk.bold(' Available types:'));
316
- for (const [key, gen] of Object.entries(GENERATORS)) {
317
- console.log(` ${chalk.cyan(key.padEnd(12))} ${chalk.dim(gen.description)}`);
318
- }
319
- console.log('');
320
- console.log(chalk.dim(' Usage: objectstack generate <type> <name>'));
321
- console.log(chalk.dim(' Example: objectstack generate object project'));
322
- console.log(chalk.dim(' Alias: os g object project'));
323
- process.exit(1);
324
- }
325
-
326
- const dir = flags.dir || generator.defaultDir;
327
- const fileName = `${toSnakeCase(name)}.ts`;
328
- const filePath = path.join(process.cwd(), dir, fileName);
329
-
330
- console.log(` ${chalk.dim('Type:')} ${chalk.cyan(type)} — ${generator.description}`);
331
- console.log(` ${chalk.dim('Name:')} ${chalk.white(name)}`);
332
- console.log(` ${chalk.dim('File:')} ${chalk.white(path.join(dir, fileName))}`);
333
- console.log('');
334
-
335
- if (flags.dryRun) {
336
- printInfo('Dry run — no files written');
337
- console.log('');
338
- console.log(chalk.dim(' Content:'));
339
- console.log(chalk.dim(' ' + '-'.repeat(38)));
340
- const content = generator.generate(name);
341
- for (const line of content.split('\n')) {
342
- console.log(chalk.dim(` ${line}`));
343
- }
344
- console.log('');
345
- return;
346
- }
347
-
348
- // Check if file exists
349
- if (fs.existsSync(filePath)) {
350
- printError(`File already exists: ${filePath}`);
351
- process.exit(1);
352
- }
353
-
354
- try {
355
- // Create directory
356
- const fullDir = path.dirname(filePath);
357
- if (!fs.existsSync(fullDir)) {
358
- fs.mkdirSync(fullDir, { recursive: true });
359
- }
360
-
361
- // Write file
362
- const content = generator.generate(name);
363
- fs.writeFileSync(filePath, content);
364
- printSuccess(`Created ${path.join(dir, fileName)}`);
365
-
366
- // Check for barrel index
367
- const indexPath = path.join(process.cwd(), dir, 'index.ts');
368
- if (fs.existsSync(indexPath)) {
369
- const indexContent = fs.readFileSync(indexPath, 'utf-8');
370
- const exportLine = `export { default as ${toCamelCase(name)} } from './${toSnakeCase(name)}';`;
371
-
372
- if (!indexContent.includes(toCamelCase(name))) {
373
- fs.appendFileSync(indexPath, exportLine + '\n');
374
- printSuccess(`Updated ${dir}/index.ts with export`);
375
- }
376
- } else {
377
- // Create barrel index
378
- const exportLine = `export { default as ${toCamelCase(name)} } from './${toSnakeCase(name)}';\n`;
379
- fs.writeFileSync(indexPath, exportLine);
380
- printSuccess(`Created ${dir}/index.ts`);
381
- }
382
-
383
- console.log('');
384
- console.log(chalk.dim(` Tip: Run \`objectstack validate\` to check your config`));
385
- console.log('');
386
-
387
- } catch (error: any) {
388
- printError(error.message || String(error));
389
- process.exit(1);
390
- }
391
- }
392
-
393
- async function runTypesGeneration(configPath: string | undefined, flags: { output: string; dryRun?: boolean }): Promise<void> {
394
- printHeader('Generate Types');
395
-
396
- try {
397
- const { loadConfig } = await import('../utils/config.js');
398
- printInfo('Loading configuration...');
399
- const { config, absolutePath } = await loadConfig(configPath);
400
-
401
- console.log(` ${chalk.dim('Config:')} ${chalk.white(absolutePath)}`);
402
- console.log(` ${chalk.dim('Output:')} ${chalk.white(flags.output)}`);
403
- console.log('');
404
-
405
- const content = generateTypesFromConfig(config as Record<string, unknown>);
406
-
407
- if (flags.dryRun) {
408
- printInfo('Dry run — no files written');
409
- console.log('');
410
- for (const line of content.split('\n')) {
411
- console.log(chalk.dim(` ${line}`));
412
- }
413
- console.log('');
414
- return;
415
- }
416
-
417
- const outPath = path.resolve(process.cwd(), flags.output);
418
- const outDir = path.dirname(outPath);
419
- if (!fs.existsSync(outDir)) {
420
- fs.mkdirSync(outDir, { recursive: true });
421
- }
422
- fs.writeFileSync(outPath, content);
423
- printSuccess(`Generated types at ${flags.output}`);
424
- console.log('');
425
-
426
- } catch (error: any) {
427
- printError(error.message || String(error));
428
- process.exit(1);
429
- }
430
- }
431
-
432
- // ─── Client SDK Generator ───────────────────────────────────────────
433
-
434
- function generateClientFromConfig(config: Record<string, unknown>): string {
435
- const lines: string[] = [
436
- '// Auto-generated by ObjectStack CLI — do not edit manually',
437
- `// Generated at ${new Date().toISOString()}`,
438
- '',
439
- "import type { Data } from '@objectstack/spec';",
440
- '',
441
- ];
442
-
443
- const objects: Record<string, unknown>[] = [];
444
- const rawObjects = (config as any).objects ?? (config as any).data?.objects ?? {};
445
-
446
- if (Array.isArray(rawObjects)) {
447
- objects.push(...rawObjects);
448
- } else if (typeof rawObjects === 'object') {
449
- for (const val of Object.values(rawObjects)) {
450
- if (val && typeof val === 'object') objects.push(val as Record<string, unknown>);
451
- }
452
- }
453
-
454
- if (objects.length === 0) {
455
- lines.push('// No objects found in configuration');
456
- return lines.join('\n') + '\n';
457
- }
458
-
459
- // Generate type interfaces
460
- for (const obj of objects) {
461
- const name = String(obj.name || 'unknown');
462
- const typeName = name
463
- .split('_')
464
- .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
465
- .join('');
466
- const fields = (obj.fields ?? {}) as Record<string, Record<string, unknown>>;
467
-
468
- lines.push(`export interface ${typeName}Record {`);
469
- lines.push(' id: string;');
470
-
471
- for (const [fieldName, fieldDef] of Object.entries(fields)) {
472
- const fType = String(fieldDef.type || 'text');
473
- const tsType = fieldTypeToTs(fType, !!fieldDef.multiple);
474
- const required = fieldDef.required ? '' : '?';
475
- lines.push(` ${fieldName}${required}: ${tsType};`);
476
- }
477
-
478
- lines.push('}');
479
- lines.push('');
480
- }
481
-
482
- // Generate client class
483
- lines.push('export class ObjectStackClient {');
484
- lines.push(' constructor(private baseUrl: string, private headers: Record<string, string> = {}) {}');
485
- lines.push('');
486
- lines.push(' private async request<T>(method: string, path: string, body?: unknown): Promise<T> {');
487
- lines.push(' const res = await fetch(`${this.baseUrl}${path}`, {');
488
- lines.push(' method,');
489
- lines.push(" headers: { 'Content-Type': 'application/json', ...this.headers },");
490
- lines.push(' body: body ? JSON.stringify(body) : undefined,');
491
- lines.push(' });');
492
- lines.push(' if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);');
493
- lines.push(' return res.json() as Promise<T>;');
494
- lines.push(' }');
495
-
496
- for (const obj of objects) {
497
- const name = String(obj.name || 'unknown');
498
- const typeName = name
499
- .split('_')
500
- .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
501
- .join('');
502
- const endpoint = `/api/${name}`;
503
-
504
- lines.push('');
505
- lines.push(` async list${typeName}(): Promise<${typeName}Record[]> {`);
506
- lines.push(` return this.request<${typeName}Record[]>('GET', '${endpoint}');`);
507
- lines.push(' }');
508
- lines.push('');
509
- lines.push(` async get${typeName}(id: string): Promise<${typeName}Record> {`);
510
- lines.push(` return this.request<${typeName}Record>('GET', '${endpoint}/\${id}');`);
511
- lines.push(' }');
512
- lines.push('');
513
- lines.push(` async create${typeName}(data: Omit<${typeName}Record, 'id'>): Promise<${typeName}Record> {`);
514
- lines.push(` return this.request<${typeName}Record>('POST', '${endpoint}', data);`);
515
- lines.push(' }');
516
- lines.push('');
517
- lines.push(` async update${typeName}(id: string, data: Partial<${typeName}Record>): Promise<${typeName}Record> {`);
518
- lines.push(` return this.request<${typeName}Record>('PATCH', '${endpoint}/\${id}', data);`);
519
- lines.push(' }');
520
- lines.push('');
521
- lines.push(` async delete${typeName}(id: string): Promise<void> {`);
522
- lines.push(` return this.request<void>('DELETE', '${endpoint}/\${id}');`);
523
- lines.push(' }');
524
- }
525
-
526
- lines.push('}');
527
- lines.push('');
528
-
529
- return lines.join('\n') + '\n';
530
- }
531
-
532
- async function runClientGeneration(configPath: string | undefined, flags: { output: string; dryRun?: boolean }): Promise<void> {
533
- printHeader('Generate Client SDK');
534
-
535
- try {
536
- const { loadConfig } = await import('../utils/config.js');
537
- const timer = createTimer();
538
- printInfo('Loading configuration...');
539
- const { config, absolutePath } = await loadConfig(configPath);
540
-
541
- console.log(` ${chalk.dim('Config:')} ${chalk.white(absolutePath)}`);
542
- console.log(` ${chalk.dim('Output:')} ${chalk.white(flags.output)}`);
543
- console.log('');
544
-
545
- printStep('Generating client SDK...');
546
- const content = generateClientFromConfig(config as Record<string, unknown>);
547
-
548
- if (flags.dryRun) {
549
- printInfo('Dry run — no files written');
550
- console.log('');
551
- for (const line of content.split('\n')) {
552
- console.log(chalk.dim(` ${line}`));
553
- }
554
- console.log('');
555
- return;
556
- }
557
-
558
- const outPath = path.resolve(process.cwd(), flags.output);
559
- const outDir = path.dirname(outPath);
560
- if (!fs.existsSync(outDir)) {
561
- fs.mkdirSync(outDir, { recursive: true });
562
- }
563
- fs.writeFileSync(outPath, content);
564
- printSuccess(`Generated client SDK at ${flags.output} (${timer.display()})`);
565
- console.log('');
566
-
567
- } catch (error: any) {
568
- printError(error.message || String(error));
569
- process.exit(1);
570
- }
571
- }
572
-
573
- // ─── Migration Generator ────────────────────────────────────────────
574
-
575
- const FIELD_TYPE_SQL_MAP: Record<string, string> = {
576
- text: 'VARCHAR(255)',
577
- textarea: 'TEXT',
578
- richtext: 'TEXT',
579
- html: 'TEXT',
580
- markdown: 'TEXT',
581
- number: 'DECIMAL(18,2)',
582
- integer: 'INTEGER',
583
- currency: 'DECIMAL(18,2)',
584
- percent: 'DECIMAL(5,2)',
585
- boolean: 'BOOLEAN',
586
- date: 'DATE',
587
- datetime: 'TIMESTAMP',
588
- time: 'TIME',
589
- email: 'VARCHAR(255)',
590
- phone: 'VARCHAR(50)',
591
- url: 'VARCHAR(2048)',
592
- select: 'VARCHAR(255)',
593
- multiselect: 'TEXT',
594
- lookup: 'VARCHAR(36)',
595
- master_detail: 'VARCHAR(36)',
596
- formula: 'TEXT',
597
- autonumber: 'SERIAL',
598
- json: 'JSONB',
599
- file: 'VARCHAR(2048)',
600
- image: 'VARCHAR(2048)',
601
- password: 'VARCHAR(255)',
602
- slug: 'VARCHAR(255)',
603
- uuid: 'UUID',
604
- ip_address: 'VARCHAR(45)',
605
- color: 'VARCHAR(7)',
606
- rating: 'INTEGER',
607
- geo_point: 'POINT',
608
- vector: 'VECTOR',
609
- encrypted: 'TEXT',
610
- };
611
-
612
- function fieldTypeToSql(fieldType: string): string {
613
- return FIELD_TYPE_SQL_MAP[fieldType] || 'TEXT';
614
- }
615
-
616
- function generateMigrationSql(config: Record<string, unknown>): string {
617
- const lines: string[] = [
618
- '-- Auto-generated by ObjectStack CLI — do not edit manually',
619
- `-- Generated at ${new Date().toISOString()}`,
620
- '',
621
- ];
622
-
623
- const objects: Record<string, unknown>[] = [];
624
- const rawObjects = (config as any).objects ?? (config as any).data?.objects ?? {};
625
-
626
- if (Array.isArray(rawObjects)) {
627
- objects.push(...rawObjects);
628
- } else if (typeof rawObjects === 'object') {
629
- for (const val of Object.values(rawObjects)) {
630
- if (val && typeof val === 'object') objects.push(val as Record<string, unknown>);
631
- }
632
- }
633
-
634
- if (objects.length === 0) {
635
- lines.push('-- No objects found in configuration');
636
- return lines.join('\n') + '\n';
637
- }
638
-
639
- for (const obj of objects) {
640
- const tableName = String(obj.name || 'unknown');
641
- const fields = (obj.fields ?? {}) as Record<string, Record<string, unknown>>;
642
-
643
- lines.push(`CREATE TABLE IF NOT EXISTS "${tableName}" (`);
644
- lines.push(' "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),');
645
-
646
- const fieldLines: string[] = [];
647
- for (const [fieldName, fieldDef] of Object.entries(fields)) {
648
- const sqlType = fieldTypeToSql(String(fieldDef.type || 'text'));
649
- const notNull = fieldDef.required ? ' NOT NULL' : '';
650
- fieldLines.push(` "${fieldName}" ${sqlType}${notNull}`);
651
- }
652
-
653
- fieldLines.push(' "created_at" TIMESTAMP NOT NULL DEFAULT now()');
654
- fieldLines.push(' "updated_at" TIMESTAMP NOT NULL DEFAULT now()');
655
- lines.push(fieldLines.join(',\n'));
656
- lines.push(');');
657
- lines.push('');
658
- }
659
-
660
- return lines.join('\n') + '\n';
661
- }
662
-
663
- function generateMigrationTs(config: Record<string, unknown>): string {
664
- const lines: string[] = [
665
- '// Auto-generated by ObjectStack CLI — do not edit manually',
666
- `// Generated at ${new Date().toISOString()}`,
667
- '',
668
- 'export async function up(db: any): Promise<void> {',
669
- ];
670
-
671
- const objects: Record<string, unknown>[] = [];
672
- const rawObjects = (config as any).objects ?? (config as any).data?.objects ?? {};
673
-
674
- if (Array.isArray(rawObjects)) {
675
- objects.push(...rawObjects);
676
- } else if (typeof rawObjects === 'object') {
677
- for (const val of Object.values(rawObjects)) {
678
- if (val && typeof val === 'object') objects.push(val as Record<string, unknown>);
679
- }
680
- }
681
-
682
- if (objects.length === 0) {
683
- lines.push(' // No objects found in configuration');
684
- lines.push('}');
685
- lines.push('');
686
- lines.push('export async function down(db: any): Promise<void> {');
687
- lines.push(' // No objects found in configuration');
688
- lines.push('}');
689
- return lines.join('\n') + '\n';
690
- }
691
-
692
- for (const obj of objects) {
693
- const tableName = String(obj.name || 'unknown');
694
- const fields = (obj.fields ?? {}) as Record<string, Record<string, unknown>>;
695
-
696
- lines.push(` await db.schema.createTable('${tableName}', (table: any) => {`);
697
- lines.push(" table.uuid('id').primary().defaultTo(db.fn.uuid());");
698
-
699
- for (const [fieldName, fieldDef] of Object.entries(fields)) {
700
- const fType = String(fieldDef.type || 'text');
701
- const required = fieldDef.required ? '.notNullable()' : '.nullable()';
702
- let colMethod: string;
703
-
704
- switch (fType) {
705
- case 'text': case 'email': case 'phone': case 'url': case 'select':
706
- case 'slug': case 'password': case 'color': case 'ip_address':
707
- colMethod = `table.string('${fieldName}')`;
708
- break;
709
- case 'textarea': case 'richtext': case 'html': case 'markdown':
710
- case 'formula': case 'encrypted':
711
- colMethod = `table.text('${fieldName}')`;
712
- break;
713
- case 'number': case 'currency': case 'percent':
714
- colMethod = `table.decimal('${fieldName}')`;
715
- break;
716
- case 'integer': case 'rating':
717
- colMethod = `table.integer('${fieldName}')`;
718
- break;
719
- case 'boolean':
720
- colMethod = `table.boolean('${fieldName}')`;
721
- break;
722
- case 'date':
723
- colMethod = `table.date('${fieldName}')`;
724
- break;
725
- case 'datetime':
726
- colMethod = `table.timestamp('${fieldName}')`;
727
- break;
728
- case 'time':
729
- colMethod = `table.time('${fieldName}')`;
730
- break;
731
- case 'json': case 'multiselect':
732
- colMethod = `table.jsonb('${fieldName}')`;
733
- break;
734
- case 'uuid': case 'lookup': case 'master_detail':
735
- colMethod = `table.uuid('${fieldName}')`;
736
- break;
737
- default:
738
- colMethod = `table.text('${fieldName}')`;
739
- }
740
-
741
- lines.push(` ${colMethod}${required};`);
742
- }
743
-
744
- lines.push(" table.timestamps(true, true);");
745
- lines.push(' });');
746
- }
747
-
748
- lines.push('}');
749
- lines.push('');
750
- lines.push('export async function down(db: any): Promise<void> {');
751
-
752
- // Drop tables in reverse order
753
- const tableNames = objects.map(o => String(o.name || 'unknown')).reverse();
754
- for (const tableName of tableNames) {
755
- lines.push(` await db.schema.dropTableIfExists('${tableName}');`);
756
- }
757
-
758
- lines.push('}');
759
-
760
- return lines.join('\n') + '\n';
761
- }
762
-
763
- async function runMigrationGeneration(configPath: string | undefined, flags: { output?: string; format: string; dryRun?: boolean }): Promise<void> {
764
- printHeader('Generate Migration');
765
-
766
- try {
767
- const { loadConfig } = await import('../utils/config.js');
768
- const timer = createTimer();
769
- printInfo('Loading configuration...');
770
- const { config, absolutePath } = await loadConfig(configPath);
771
-
772
- const ext = flags.format === 'sql' ? 'sql' : 'ts';
773
- // Format: YYYYMMDDHHmmss (e.g. 20250101120000)
774
- const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
775
- const defaultOutput = `migrations/${timestamp}_migration.${ext}`;
776
- const output = flags.output || defaultOutput;
777
-
778
- console.log(` ${chalk.dim('Config:')} ${chalk.white(absolutePath)}`);
779
- console.log(` ${chalk.dim('Format:')} ${chalk.white(flags.format)}`);
780
- console.log(` ${chalk.dim('Output:')} ${chalk.white(output)}`);
781
- console.log('');
782
-
783
- printStep('Generating migration...');
784
- const content = flags.format === 'sql'
785
- ? generateMigrationSql(config as Record<string, unknown>)
786
- : generateMigrationTs(config as Record<string, unknown>);
787
-
788
- if (flags.dryRun) {
789
- printInfo('Dry run — no files written');
790
- console.log('');
791
- for (const line of content.split('\n')) {
792
- console.log(chalk.dim(` ${line}`));
793
- }
794
- console.log('');
795
- return;
796
- }
797
-
798
- const outPath = path.resolve(process.cwd(), output);
799
- const outDir = path.dirname(outPath);
800
- if (!fs.existsSync(outDir)) {
801
- fs.mkdirSync(outDir, { recursive: true });
802
- }
803
- fs.writeFileSync(outPath, content);
804
- printSuccess(`Generated migration at ${output} (${timer.display()})`);
805
- console.log('');
806
-
807
- } catch (error: any) {
808
- printError(error.message || String(error));
809
- process.exit(1);
810
- }
811
- }
812
-
813
- // ─── JSON Schema Generator ──────────────────────────────────────────
814
-
815
- async function runSchemaGeneration(flags: { output: string; dryRun?: boolean }): Promise<void> {
816
- printHeader('Generate Schema');
817
-
818
- try {
819
- const timer = createTimer();
820
- printStep('Loading ObjectStackDefinitionSchema...');
821
-
822
- const { z } = await import('zod');
823
- const { ObjectStackDefinitionSchema } = await import('@objectstack/spec');
824
-
825
- printStep('Converting to JSON Schema...');
826
- const jsonSchema = z.toJSONSchema(ObjectStackDefinitionSchema, {
827
- target: 'draft-2020-12',
828
- });
829
-
830
- // Add metadata
831
- const schema = {
832
- ...jsonSchema,
833
- $id: 'https://schema.objectstack.io/objectstack.config.json',
834
- title: 'ObjectStack Configuration',
835
- description: 'JSON Schema for objectstack.config.ts — generated from ObjectStackDefinitionSchema',
836
- };
837
-
838
- const content = JSON.stringify(schema, null, 2) + '\n';
839
-
840
- if (flags.dryRun) {
841
- printInfo('Dry run — no files written');
842
- console.log('');
843
- console.log(content);
844
- return;
845
- }
846
-
847
- const outPath = path.resolve(process.cwd(), flags.output);
848
- const outDir = path.dirname(outPath);
849
- if (!fs.existsSync(outDir)) {
850
- fs.mkdirSync(outDir, { recursive: true });
851
- }
852
- fs.writeFileSync(outPath, content);
853
- printSuccess(`Generated JSON Schema at ${flags.output} (${timer.display()})`);
854
- console.log('');
855
- console.log(chalk.dim(' Usage: Reference in your IDE or editor for autocomplete'));
856
- console.log(chalk.dim(` Path: ${outPath}`));
857
- console.log('');
858
-
859
- } catch (error: any) {
860
- printError(error.message || String(error));
861
- process.exit(1);
862
- }
863
- }
864
-
865
- // ─── Main Generate Command ──────────────────────────────────────────
866
-
867
- export default class Generate extends Command {
868
- static override description = 'Generate metadata files or TypeScript types';
869
-
870
- static override aliases = ['g'];
871
-
872
- static override args = {
873
- type: Args.string({ description: 'Metadata type to generate (object, view, action, flow, agent, dashboard, app)', required: true }),
874
- name: Args.string({ description: 'Name for the metadata (use kebab-case)', required: false }),
875
- };
876
-
877
- static override flags = {
878
- dir: Flags.string({ char: 'd', description: 'Target directory (overrides default)' }),
879
- 'dry-run': Flags.boolean({ description: 'Show what would be created without writing files' }),
880
- output: Flags.string({ char: 'o', description: 'Output file path' }),
881
- format: Flags.string({ description: 'Output format: sql or typescript', default: 'typescript' }),
882
- };
883
-
884
- async run(): Promise<void> {
885
- const { args, flags } = await this.parse(Generate);
886
-
887
- // Route to sub-commands by type name
888
- switch (args.type) {
889
- case 'types':
890
- return runTypesGeneration(args.name, {
891
- output: flags.output ?? 'src/types/objectstack.d.ts',
892
- dryRun: flags['dry-run'],
893
- });
894
- case 'client':
895
- return runClientGeneration(args.name, {
896
- output: flags.output ?? 'src/client/objectstack-client.ts',
897
- dryRun: flags['dry-run'],
898
- });
899
- case 'migration':
900
- return runMigrationGeneration(args.name, {
901
- output: flags.output,
902
- format: flags.format ?? 'typescript',
903
- dryRun: flags['dry-run'],
904
- });
905
- case 'schema':
906
- return runSchemaGeneration({
907
- output: flags.output ?? 'objectstack.schema.json',
908
- dryRun: flags['dry-run'],
909
- });
910
- }
911
-
912
- // Metadata generation
913
- if (!args.name) {
914
- printError('Missing required argument: <name>');
915
- console.log(chalk.dim(' Usage: objectstack generate <type> <name>'));
916
- process.exit(1);
917
- }
918
-
919
- await runMetadataGeneration(args.type, args.name, {
920
- dir: flags.dir,
921
- dryRun: flags['dry-run'],
922
- });
923
- }
924
- }