@slingr/cli 0.0.2 → 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 -27
  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
@@ -9,22 +9,121 @@ const node_child_process_1 = require("node:child_process");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const datasourceParser_js_1 = require("../utils/datasourceParser.js");
11
11
  const portChecker_js_1 = require("../utils/portChecker.js");
12
+ const checkFramework_js_1 = require("../utils/checkFramework.js");
13
+ const viewsGenerator_js_1 = require("../utils/viewsGenerator.js");
14
+ const buildCache_js_1 = require("../utils/buildCache.js");
12
15
  class Run extends core_1.Command {
13
- static description = 'Run a Slingr application locally';
16
+ static description = 'Run a Slingr application locally with both backend and frontend on a single port.\n\n' +
17
+ 'By default, runs in production mode (unified server). Use --dev flag for hot-reload development with separate processes.';
14
18
  static examples = [
15
19
  '<%= config.bin %> <%= command.id %>',
16
20
  '<%= config.bin %> <%= command.id %> --skip-infra',
21
+ '<%= config.bin %> <%= command.id %> --dev',
22
+ '<%= config.bin %> <%= command.id %> --ui-only',
23
+ '<%= config.bin %> <%= command.id %> --backend',
17
24
  ];
18
25
  static flags = {
19
26
  help: core_1.Flags.help({ char: 'h' }),
20
27
  'skip-infra': core_1.Flags.boolean({
21
28
  char: 'i',
22
- description: 'Skip infrastructure setup and checks',
29
+ description: 'Skip infrastructure setup and checks (use when database containers are already running)',
30
+ required: false,
31
+ }),
32
+ dev: core_1.Flags.boolean({
33
+ char: 'd',
34
+ description: 'Run in development mode with hot-reload (starts UI dev server and backend separately). Mutually exclusive with --ui-only and --backend.',
35
+ required: false,
36
+ exclusive: ['ui-only', 'backend'],
37
+ }),
38
+ 'ui-only': core_1.Flags.boolean({
39
+ description: 'Start only the UI development server (backend must be running separately). Mutually exclusive with --dev and --backend.',
40
+ required: false,
41
+ exclusive: ['dev', 'backend'],
42
+ }),
43
+ backend: core_1.Flags.boolean({
44
+ char: 'b',
45
+ description: 'Start only the backend server (use with --ui-only for split development). Mutually exclusive with --dev and --ui-only.',
46
+ required: false,
47
+ exclusive: ['dev', 'ui-only'],
48
+ }),
49
+ port: core_1.Flags.integer({
50
+ char: 'p',
51
+ description: 'Custom port for the server (default: 3000 or first available)',
52
+ required: false,
53
+ }),
54
+ verbose: core_1.Flags.boolean({
55
+ char: 'v',
56
+ description: 'Show detailed build and setup output and enable DEBUG level logs',
23
57
  required: false,
24
58
  }),
25
59
  };
60
+ verbose = false;
61
+ stepResults = [];
62
+ summarized = false;
63
+ /** Log only when --verbose is active. */
64
+ vlog(msg) {
65
+ if (this.verbose) {
66
+ this.log(msg);
67
+ }
68
+ }
69
+ /** Record the result of a step for the final summary. */
70
+ recordStep(name, status, message) {
71
+ const step = { name, status };
72
+ if (message !== undefined)
73
+ step.message = message;
74
+ this.stepResults.push(step);
75
+ }
76
+ /** Extract build output from an execSync error (includes both stdout and stderr). */
77
+ extractBuildOutput(error) {
78
+ const parts = [];
79
+ if (error?.stdout) {
80
+ const stdout = error.stdout.toString().trim();
81
+ if (stdout)
82
+ parts.push(stdout);
83
+ }
84
+ if (error?.stderr) {
85
+ const stderr = error.stderr.toString().trim();
86
+ if (stderr)
87
+ parts.push(stderr);
88
+ }
89
+ return parts.length > 0 ? '\n' + parts.join('\n') : '';
90
+ }
91
+ /** Print a summary of all recorded steps. */
92
+ printSummary() {
93
+ if (this.stepResults.length === 0 || this.summarized)
94
+ return;
95
+ this.summarized = true;
96
+ const hasErrors = this.stepResults.some(s => s.status === 'error');
97
+ const hasWarnings = this.stepResults.some(s => s.status === 'warning');
98
+ this.log('');
99
+ this.log('━'.repeat(60));
100
+ if (hasErrors) {
101
+ this.log('āŒ Process completed with errors');
102
+ }
103
+ else if (hasWarnings) {
104
+ this.log('āš ļø Process completed with warnings');
105
+ }
106
+ else {
107
+ this.log('āœ… All steps completed successfully');
108
+ }
109
+ this.log('━'.repeat(60));
110
+ for (const step of this.stepResults) {
111
+ const icon = step.status === 'success' ? 'āœ…'
112
+ : step.status === 'warning' ? 'āš ļø '
113
+ : step.status === 'skipped' ? 'ā­ļø '
114
+ : 'āŒ';
115
+ const msg = step.status === 'skipped'
116
+ ? ` — skipped${step.message ? ` (${step.message})` : ''}`
117
+ : step.message ? ` — ${step.message}` : '';
118
+ this.log(` ${icon} ${step.name}${msg}`);
119
+ }
120
+ this.log('━'.repeat(60));
121
+ this.log('');
122
+ }
26
123
  async run() {
27
124
  const { flags } = await this.parse(Run);
125
+ this.verbose = flags.verbose ?? false;
126
+ process.env.SLINGR_VERBOSE = this.verbose ? 'true' : 'false';
28
127
  try {
29
128
  // Check if we're in a Slingr app directory
30
129
  const packageJsonPath = node_path_1.default.join(process.cwd(), 'package.json');
@@ -32,154 +131,144 @@ class Run extends core_1.Command {
32
131
  this.error("Not in a Slingr application directory. Please run this command from your app's root directory.");
33
132
  }
34
133
  const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
35
- if (!packageJson.dependencies?.['slingr-framework']) {
134
+ if (!(await (0, checkFramework_js_1.hasSlingrFramework)())) {
36
135
  this.error('This directory does not contain a Slingr application.');
37
136
  }
38
- // Check if src/index.ts exists
39
- const srcIndexPath = node_path_1.default.join(process.cwd(), 'src', 'index.ts');
40
- if (!(await fs_extra_1.default.pathExists(srcIndexPath))) {
41
- this.error('src/index.ts not found. This does not appear to be a valid Slingr application.');
137
+ // Verify entrypoint exists
138
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
139
+ const srcAppPath = node_path_1.default.join(backendPath, 'src', 'App.ts');
140
+ if (!(await fs_extra_1.default.pathExists(srcAppPath))) {
141
+ this.error('backend/src/App.ts not found. This does not appear to be a valid Slingr application.');
42
142
  }
43
- // Step 1: Build slingr-framework
143
+ // 1. Build/Prepare Framework (Conditioned)
44
144
  await this.buildFramework();
45
- // Step 2: Generate actions index
46
- await this.generateActionsIndex();
47
- // Step 3: Generate code
48
- await this.generateCode();
49
- // Step 4: Update and check infrastructure
50
- if (!flags['skip-infra']) {
51
- await this.checkInfrastructure();
145
+ // 2. Execution Mode
146
+ if (flags['ui-only']) {
147
+ await this.runUIOnly(flags.port);
148
+ }
149
+ else if (flags.backend) {
150
+ await this.runBackendOnly(flags);
151
+ }
152
+ else if (flags.dev) {
153
+ await this.runDevMode(flags);
154
+ }
155
+ else {
156
+ // Production / Default Unified Mode - needs UI
157
+ await (0, viewsGenerator_js_1.generateViewsContext)(process.cwd(), this.verbose);
158
+ await this.buildUI();
159
+ await this.generateCode();
160
+ if (!flags['skip-infra']) {
161
+ await this.checkInfrastructure();
162
+ }
163
+ else {
164
+ this.recordStep('Infrastructure', 'skipped', '--skip-infra');
165
+ }
166
+ this.printSummary();
167
+ await this.executeApp(flags.port, {}, true); // true = production mode
52
168
  }
53
- // Step 4: Execute the application
54
- await this.executeApp();
55
169
  }
56
170
  catch (error) {
171
+ this.printSummary();
57
172
  this.error(error.message);
58
173
  }
59
174
  }
60
- async buildFramework(silent = false) {
61
- const currentDir = process.cwd();
62
- const nodeModulesPath = node_path_1.default.join(currentDir, 'node_modules', 'slingr-framework');
63
- if (!silent) {
64
- this.log(`Looking for slingr-framework in: ${nodeModulesPath}`);
65
- }
66
- if (!(await fs_extra_1.default.pathExists(nodeModulesPath))) {
67
- this.error('slingr-framework not found in node_modules. Please run npm install first.');
175
+ /**
176
+ * Helper to resolve where the compiled UI is located.
177
+ */
178
+ async resolveUiPath() {
179
+ const cwd = process.cwd();
180
+ // Check for app's frontend/dist directory
181
+ const frontendDistPath = node_path_1.default.join(cwd, 'frontend', 'dist');
182
+ if (await fs_extra_1.default.pathExists(node_path_1.default.join(frontendDistPath, 'index.html'))) {
183
+ return { path: frontendDistPath, exists: true };
68
184
  }
69
- // For linked packages, the framework should already be built
70
- // Just check if it's available
185
+ // UI not found
186
+ return { path: frontendDistPath, exists: false };
187
+ }
188
+ async buildFramework(silent = false) {
189
+ // Only attempts to build if it detects it's a local linked dependency,
190
+ // otherwise just verifies existence.
71
191
  try {
72
- require.resolve('slingr-framework');
73
- if (!silent) {
74
- this.log('āœ… slingr-framework is available');
192
+ const frameworkPath = require.resolve('@slingr/framework-backend');
193
+ if (frameworkPath && !silent && this.verbose) {
194
+ this.log('āœ… @slingr/framework-backend is available');
75
195
  }
76
196
  }
77
197
  catch (error) {
78
- this.error('slingr-framework module cannot be loaded. Please ensure it is properly installed.');
79
- }
80
- }
81
- async checkGraphQLSetup() {
82
- const schemaPath = node_path_1.default.join(process.cwd(), 'generated', 'gql', 'schema.graphql');
83
- const sdkPath = node_path_1.default.join(process.cwd(), 'generated', 'gql', 'sdk.ts');
84
- const indexPath = node_path_1.default.join(process.cwd(), 'generated', 'gql', 'index.ts');
85
- const schemaExists = await fs_extra_1.default.pathExists(schemaPath);
86
- const sdkExists = await fs_extra_1.default.pathExists(sdkPath);
87
- const indexExists = await fs_extra_1.default.pathExists(indexPath);
88
- if (!schemaExists || !sdkExists || !indexExists) {
89
- this.log('');
90
- this.log('āš ļø GraphQL schema and SDK not found!');
91
- this.log('');
92
- this.log('The GraphQL server requires a generated schema and SDK to run.');
93
- this.log('');
94
- if (!schemaExists) {
95
- this.log('āŒ Missing: generated/gql/schema.graphql');
96
- }
97
- if (!sdkExists) {
98
- this.log('āŒ Missing: generated/gql/sdk.ts');
99
- }
100
- if (!indexExists) {
101
- this.log('āŒ Missing: generated/gql/index.ts');
198
+ const nodeModulesPath = node_path_1.default.join(process.cwd(), 'node_modules', '@slingr');
199
+ if (!(await fs_extra_1.default.pathExists(nodeModulesPath))) {
200
+ this.error('@slingr/framework-backend not found. Please run npm install.');
102
201
  }
103
- this.log('');
104
- this.log('šŸ“‹ To generate the required files, run:');
105
- this.log('');
106
- this.log(' 1. Generate GraphQL schema:');
107
- this.log(' $ slingr gql generate-schema');
108
- this.log('');
109
- this.log(' 2. Generate SDK:');
110
- this.log(' $ slingr gql generate-sdk');
111
- this.log('');
112
- this.log(' 3. Then run the server:');
113
- this.log(' $ slingr run');
114
- this.log('');
115
- this.error('GraphQL setup incomplete. Please run the commands above.');
116
202
  }
117
- this.log('āœ… GraphQL schema and SDK found');
118
203
  }
119
204
  async checkInfrastructure() {
120
205
  const dataSources = await this.loadDataSources();
121
- // Check if docker is installed
206
+ // Check Docker availability
122
207
  try {
123
208
  (0, node_child_process_1.execSync)('docker --version', { stdio: 'pipe' });
124
209
  }
125
210
  catch {
126
- this.error('Docker is not installed. Please install Docker to run infrastructure services.');
127
- }
128
- // Check if Docker Engine is running BEFORE checking ports
129
- if (!(0, portChecker_js_1.isDockerRunning)()) {
130
- this.error('🐳 Docker is not running. Please start Docker and try again.\n\n' +
131
- 'On macOS/Windows: Start Docker Desktop\n' +
132
- 'On Linux: Run "sudo systemctl start docker" or "sudo service docker start"');
211
+ this.recordStep('Infrastructure', 'error', 'Docker is not installed');
212
+ this.error('Docker is not installed.');
133
213
  }
134
- // Check if docker-compose is installed
214
+ const { ensureDockerIsRunning } = await import('../utils/dockerManager.js');
135
215
  try {
136
- (0, node_child_process_1.execSync)('docker compose version', { stdio: 'pipe' });
216
+ await ensureDockerIsRunning('slingr run');
137
217
  }
138
- catch {
139
- this.error('Docker Compose is not installed. Please install Docker Compose to run infrastructure services.');
218
+ catch (error) {
219
+ this.recordStep('Infrastructure', 'error', error.message);
220
+ this.error(error.message);
140
221
  }
141
- // Check port availability before starting services
142
222
  await this.checkPortAvailability();
143
- // Run infra update command to ensure latest infrastructure configuration
144
223
  await this.config.runCommand('infra:update', ['--all']);
145
- // Start infrastructure services
146
- this.log('Starting infrastructure services...');
224
+ this.vlog('Starting infrastructure services...');
147
225
  try {
148
- (0, node_child_process_1.execSync)('docker compose up -d', { stdio: 'inherit' });
226
+ (0, node_child_process_1.execSync)('docker compose up -d', { stdio: this.verbose ? 'inherit' : 'pipe' });
149
227
  }
150
228
  catch {
151
- this.error('Failed to start Docker services. This might be due to port conflicts or other Docker issues.');
229
+ this.recordStep('Infrastructure', 'error', 'Failed to start Docker services');
230
+ this.error('Failed to start Docker services.');
152
231
  }
153
- // Wait for services to be healthy
154
- this.log('Waiting for services to be ready...');
232
+ // Health check loop
233
+ this.vlog('Waiting for services to be ready...');
234
+ let infraWarnings = [];
155
235
  for (const ds of dataSources) {
156
236
  const serviceName = `${ds.name}-db`;
157
- this.log(`Checking ${serviceName}...`);
158
237
  let attempts = 0;
159
238
  const maxAttempts = 30;
239
+ if (this.verbose) {
240
+ process.stdout.write(`Checking ${serviceName}... `);
241
+ }
160
242
  while (attempts < maxAttempts) {
161
243
  try {
162
244
  const containerInfo = (0, node_child_process_1.execSync)(`docker ps -f name=${serviceName} --format '{{.Status}}'`, {
163
245
  encoding: 'utf-8',
164
246
  });
165
247
  if (containerInfo.includes('healthy')) {
166
- this.log(`Service ${serviceName} is healthy`);
248
+ if (this.verbose) {
249
+ console.log('āœ…');
250
+ }
167
251
  break;
168
252
  }
169
253
  }
170
- catch {
171
- // Continue trying
172
- }
254
+ catch { }
173
255
  await new Promise(resolve => setTimeout(resolve, 1000));
174
256
  attempts++;
175
- if (attempts === maxAttempts) {
176
- this.error(`Service ${serviceName} is not healthy after ${maxAttempts} seconds`);
177
- }
178
257
  }
258
+ if (attempts === maxAttempts) {
259
+ this.warn(`Service ${serviceName} is not reporting healthy yet.`);
260
+ infraWarnings.push(`${serviceName} not healthy`);
261
+ }
262
+ }
263
+ if (infraWarnings.length > 0) {
264
+ this.recordStep('Infrastructure', 'warning', infraWarnings.join(', '));
265
+ }
266
+ else {
267
+ this.recordStep('Infrastructure', 'success');
179
268
  }
180
269
  }
181
270
  async checkPortAvailability() {
182
- this.log('Checking port availability for datasources...');
271
+ this.vlog('Checking port availability for datasources...');
183
272
  const dataSourcePorts = await (0, datasourceParser_js_1.extractDataSourcePorts)();
184
273
  if (dataSourcePorts.length === 0) {
185
274
  return;
@@ -188,8 +277,8 @@ class Run extends core_1.Command {
188
277
  const portUsage = await (0, portChecker_js_1.checkPortsUsage)(ports);
189
278
  const conflictingPorts = portUsage.filter(p => p.inUse && !p.isProjectDocker);
190
279
  const dockerPorts = portUsage.filter(p => p.inUse && p.isProjectDocker);
191
- // Show info about existing Docker containers
192
- if (dockerPorts.length > 0) {
280
+ // Show info about existing Docker containers (verbose only)
281
+ if (dockerPorts.length > 0 && this.verbose) {
193
282
  this.log('ā„¹ļø Found existing project containers:');
194
283
  for (const dockerPort of dockerPorts) {
195
284
  const dataSource = dataSourcePorts.find(ds => ds.port === dockerPort.port);
@@ -200,6 +289,7 @@ class Run extends core_1.Command {
200
289
  this.log('');
201
290
  }
202
291
  if (conflictingPorts.length > 0) {
292
+ // Conflict info is always shown — it precedes a fatal error
203
293
  this.log('āš ļø Port conflicts detected!');
204
294
  this.log('');
205
295
  for (const conflictPort of conflictingPorts) {
@@ -230,36 +320,375 @@ class Run extends core_1.Command {
230
320
  this.error(`Cannot start infrastructure due to port conflicts. Please resolve the port conflicts above.`);
231
321
  }
232
322
  else {
233
- this.log('āœ… All required ports are available');
323
+ this.vlog('āœ… All required ports are available');
234
324
  }
235
325
  }
236
- async generateActionsIndex() {
237
- this.log('šŸ”„ Generating actions index...');
238
- const actionsPath = node_path_1.default.join(process.cwd(), 'src', 'actions');
239
- // Check if actions directory exists
240
- if (!(await fs_extra_1.default.pathExists(actionsPath))) {
241
- this.log('ā„¹ļø No actions directory found, skipping action index generation');
242
- return;
326
+ async buildUI() {
327
+ const frontendPath = node_path_1.default.join(process.cwd(), 'frontend');
328
+ // Check if frontend directory exists
329
+ if (!(await fs_extra_1.default.pathExists(frontendPath))) {
330
+ this.error('Frontend directory not found. This application does not have a frontend project.\n\n' +
331
+ 'Expected frontend directory at: ' +
332
+ frontendPath +
333
+ '\n\n' +
334
+ 'Please ensure your Slingr application has a frontend/ directory.');
335
+ }
336
+ // Check if frontend/package.json exists
337
+ const frontendPackageJson = node_path_1.default.join(frontendPath, 'package.json');
338
+ if (!(await fs_extra_1.default.pathExists(frontendPackageJson))) {
339
+ this.error('Frontend package.json not found.\n\n' + 'Expected at: ' + frontendPackageJson);
340
+ }
341
+ const uiInfo = await this.resolveUiPath();
342
+ if (uiInfo.exists) {
343
+ this.vlog('āœ… Using pre-built UI from frontend/dist');
344
+ this.recordStep('Frontend build', 'skipped', 'Using pre-built UI');
243
345
  }
346
+ else {
347
+ this.vlog('šŸ“¦ Building frontend...');
348
+ try {
349
+ if (this.verbose) {
350
+ // Stream build output directly to the console in verbose mode
351
+ (0, node_child_process_1.execSync)('npm run build', { stdio: 'inherit', cwd: frontendPath });
352
+ }
353
+ else {
354
+ // Capture output for potential error reporting in non-verbose mode
355
+ (0, node_child_process_1.execSync)('npm run build', { stdio: 'pipe', cwd: frontendPath });
356
+ }
357
+ this.vlog('āœ… Frontend built successfully');
358
+ this.recordStep('Frontend build', 'success');
359
+ }
360
+ catch (error) {
361
+ const detail = this.extractBuildOutput(error);
362
+ this.recordStep('Frontend build', 'error', 'Compilation failed');
363
+ this.error(`Frontend build failed:${detail || ` ${error.message}`}`);
364
+ }
365
+ }
366
+ // Copy built UI to backend/dist/ui so the backend can serve it
367
+ await this.copyUIToBackend();
368
+ }
369
+ async copyUIToBackend() {
370
+ const frontendDistPath = node_path_1.default.join(process.cwd(), 'frontend', 'dist');
371
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
372
+ const backendUiPath = node_path_1.default.join(backendPath, 'dist', 'ui');
373
+ // Check if frontend/dist exists
374
+ if (!(await fs_extra_1.default.pathExists(frontendDistPath))) {
375
+ this.error('Frontend build not found.\n\n' +
376
+ 'Expected at: ' +
377
+ frontendDistPath +
378
+ '\n\n' +
379
+ 'The frontend build failed or was not created.');
380
+ }
381
+ this.vlog('šŸ“‹ Copying UI to backend/dist/ui...');
244
382
  try {
245
- // Import and call the generateActionsIndex function directly from the framework
246
- const framework = await import('slingr-framework');
247
- await framework.generateActionsIndex(process.cwd(), 'src/actions');
248
- this.log('āœ… Actions index generated successfully');
383
+ // Remove old UI if exists
384
+ await fs_extra_1.default.remove(backendUiPath);
385
+ // Create backend/dist directory if it doesn't exist
386
+ await fs_extra_1.default.ensureDir(node_path_1.default.join(backendPath, 'dist'));
387
+ // Copy frontend/dist to backend/dist/ui
388
+ await fs_extra_1.default.copy(frontendDistPath, backendUiPath);
389
+ this.vlog('āœ… UI copied to backend successfully');
249
390
  }
250
391
  catch (error) {
251
- this.warn(`Failed to generate actions index: ${error.message}`);
252
- this.warn('Continuing anyway, but actions might not be registered correctly.');
392
+ this.error(`Failed to copy UI to backend: ${error.message}`);
253
393
  }
254
394
  }
255
395
  async generateCode() {
256
- // Compile TypeScript code
257
- this.log('Compiling TypeScript code...');
258
- (0, node_child_process_1.execSync)('npm run build', { stdio: 'inherit' });
396
+ const cwd = process.cwd();
397
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
398
+ // 1. Backend TypeScript compilation
399
+ const compileOutputFile = 'backend/dist/src/App.js';
400
+ const compileUpToDate = await (0, buildCache_js_1.isUpToDate)({
401
+ cwd,
402
+ sourceGlobs: ['backend/src/**/*.ts', 'backend/package-lock.json'],
403
+ outputFiles: [compileOutputFile],
404
+ });
405
+ if (compileUpToDate) {
406
+ this.vlog('āœ… Backend compilation up-to-date, skipping');
407
+ this.recordStep('Backend compilation', 'skipped', 'Up-to-date');
408
+ }
409
+ else {
410
+ this.vlog('Compiling TypeScript code...');
411
+ try {
412
+ // Clean TypeScript output directories to remove stale compiled files from deleted models.
413
+ // Only remove src/ and generated/ (not dist/ui/ which holds the copied frontend build).
414
+ await fs_extra_1.default.remove(node_path_1.default.join(backendPath, 'dist', 'src'));
415
+ await fs_extra_1.default.remove(node_path_1.default.join(backendPath, 'dist', 'generated'));
416
+ if (this.verbose) {
417
+ // Stream build output directly to the terminal in verbose mode to avoid buffering.
418
+ (0, node_child_process_1.execSync)('npm run build', { stdio: 'inherit', cwd: backendPath });
419
+ }
420
+ else {
421
+ // Capture output in non-verbose mode so it can be inspected on failure without spamming the console.
422
+ (0, node_child_process_1.execSync)('npm run build', { stdio: 'pipe', cwd: backendPath });
423
+ }
424
+ this.recordStep('Backend compilation', 'success');
425
+ }
426
+ catch (error) {
427
+ const detail = this.extractBuildOutput(error);
428
+ this.recordStep('Backend compilation', 'error', 'Compilation failed');
429
+ this.error(`TypeScript compilation failed:${detail || ` ${error.message}`}`);
430
+ }
431
+ }
432
+ // 2. GraphQL schema generation
433
+ const schemaUpToDate = await (0, buildCache_js_1.isUpToDate)({
434
+ cwd,
435
+ sourceGlobs: [
436
+ 'backend/src/data/**/*.ts',
437
+ 'backend/src/dataSources/**/*.ts',
438
+ 'backend/src/actions/**/*.ts',
439
+ 'backend/package-lock.json',
440
+ ],
441
+ outputFiles: ['backend/generated/gql/schema.graphql'],
442
+ });
443
+ // 3. GraphQL SDK generation (depends on schema)
444
+ // Evaluate before schema runs; if schema regenerates, SDK must too (see sdkShouldRun below)
445
+ const sdkUpToDate = await (0, buildCache_js_1.isUpToDate)({
446
+ cwd,
447
+ sourceGlobs: ['backend/generated/gql/schema.graphql'],
448
+ outputFiles: ['frontend/generated/gql/types.ts'],
449
+ });
450
+ try {
451
+ if (!schemaUpToDate) {
452
+ this.vlog('Generating GraphQL schema and SDK...');
453
+ await this.config.runCommand('gql', ['generate-schema']);
454
+ }
455
+ else {
456
+ this.vlog('āœ… GraphQL schema up-to-date, skipping');
457
+ }
458
+ // Run SDK if schema was regenerated OR SDK output was stale
459
+ const sdkShouldRun = !schemaUpToDate || !sdkUpToDate;
460
+ if (sdkShouldRun) {
461
+ if (schemaUpToDate) {
462
+ this.vlog('Generating GraphQL SDK...');
463
+ }
464
+ await this.config.runCommand('gql', ['generate-sdk']);
465
+ }
466
+ else {
467
+ this.vlog('āœ… GraphQL SDK up-to-date, skipping');
468
+ }
469
+ if (schemaUpToDate && !sdkShouldRun) {
470
+ this.vlog('āœ… GraphQL schema and SDK up-to-date');
471
+ this.recordStep('GraphQL schema & SDK', 'skipped', 'Up-to-date');
472
+ }
473
+ else {
474
+ this.vlog('āœ… GraphQL schema and SDK generated');
475
+ this.recordStep('GraphQL schema & SDK', 'success');
476
+ }
477
+ }
478
+ catch (error) {
479
+ this.warn(`Warning: Failed to generate GraphQL artifacts: ${error.message}`);
480
+ this.warn('You can manually generate them with "slingr gql generate-schema" and "slingr gql generate-sdk"');
481
+ this.recordStep('GraphQL schema & SDK', 'warning', error.message);
482
+ }
483
+ }
484
+ async runDevMode(flags) {
485
+ this.log('šŸš€ Starting in development mode (Backend hot-reload + UI dev server)...');
486
+ await this.buildFramework(true);
487
+ await (0, viewsGenerator_js_1.generateViewsContext)(process.cwd(), this.verbose);
488
+ await this.generateCode();
489
+ if (!flags['skip-infra']) {
490
+ await this.checkInfrastructure();
491
+ }
492
+ else {
493
+ this.recordStep('Infrastructure', 'skipped', '--skip-infra');
494
+ }
495
+ const backendPort = flags.port || 3000;
496
+ const uiPort = flags.port || 8000;
497
+ // Check for frontend directory
498
+ const frontendPath = node_path_1.default.join(process.cwd(), 'frontend');
499
+ if (!(await fs_extra_1.default.pathExists(node_path_1.default.join(frontendPath, 'package.json')))) {
500
+ this.error('Frontend directory not found. Cannot run in development mode.\n\n' +
501
+ 'Expected frontend directory at: ' +
502
+ frontendPath);
503
+ }
504
+ // Frontend TypeScript type-check (dev servers use transpile-only, so errors don't surface otherwise)
505
+ await this.typeCheckFrontend(frontendPath);
506
+ this.log('ā„¹ļø Starting development servers:');
507
+ this.log(` • Backend with hot-reload on port ${backendPort}`);
508
+ this.log(` • UI dev server on port ${uiPort}`);
509
+ this.log('');
510
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
511
+ const env = {
512
+ ...process.env,
513
+ PORT: backendPort.toString(),
514
+ SLINGR_SKIP_UI: 'true', // Skip UI serving in dev mode - frontend runs separately
515
+ LOG_LEVEL: this.verbose ? 'debug' : process.env.LOG_LEVEL || 'info',
516
+ };
517
+ // Filter out noisy UmiJS startup messages that aren't actionable
518
+ const umiNoise = /ä½ ēŸ„é“å—|Using mako@|Mako is an extremely fast/;
519
+ const forwardStream = (src, dest) => {
520
+ src.on('data', (chunk) => {
521
+ const text = chunk.toString();
522
+ const filtered = text
523
+ .split('\n')
524
+ .filter(line => !umiNoise.test(line))
525
+ .join('\n');
526
+ if (filtered.trim() || text.endsWith('\n')) {
527
+ dest.write(filtered.endsWith('\n') || !filtered ? filtered : filtered + '\n');
528
+ }
529
+ });
530
+ };
531
+ // Start Backend Process
532
+ this.vlog('šŸ”§ Starting backend server...');
533
+ const backendArgs = ['ts-node', 'src/App.ts'];
534
+ const backendCmd = 'npx';
535
+ const backendProcess = (0, node_child_process_1.spawn)(backendCmd, backendArgs, {
536
+ stdio: ['inherit', 'pipe', 'pipe'],
537
+ cwd: backendPath,
538
+ env,
539
+ shell: true,
540
+ });
541
+ forwardStream(backendProcess.stdout, process.stdout);
542
+ forwardStream(backendProcess.stderr, process.stderr);
543
+ // Start UI Process
544
+ this.vlog('šŸŽØ Starting UI development server...');
545
+ const uiProcess = (0, node_child_process_1.spawn)('npm', ['run', 'dev'], {
546
+ stdio: ['inherit', 'pipe', 'pipe'],
547
+ cwd: frontendPath,
548
+ env: { ...process.env, PORT: uiPort.toString() },
549
+ shell: true,
550
+ });
551
+ forwardStream(uiProcess.stdout, process.stdout);
552
+ forwardStream(uiProcess.stderr, process.stderr);
553
+ // Wait for both servers to signal ready, then show the pre-flight summary at the bottom
554
+ await Promise.all([
555
+ this.waitForReady(backendProcess, /Slingr application is ready/, 15000),
556
+ this.waitForReady(uiProcess, /App listening at/, 30000),
557
+ ]);
558
+ this.printSummary();
559
+ // Graceful Shutdown
560
+ let isShuttingDown = false;
561
+ const cleanup = async () => {
562
+ if (isShuttingDown) {
563
+ return;
564
+ }
565
+ isShuttingDown = true;
566
+ this.log('\nšŸ‘‹ Shutting down development servers...');
567
+ try {
568
+ if (backendProcess) {
569
+ if (process.platform === 'win32' && backendProcess.pid) {
570
+ (0, node_child_process_1.execSync)(`taskkill /F /T /PID ${backendProcess.pid}`, { stdio: 'ignore' });
571
+ }
572
+ else {
573
+ backendProcess.kill('SIGTERM');
574
+ }
575
+ }
576
+ if (uiProcess) {
577
+ if (process.platform === 'win32' && uiProcess.pid) {
578
+ (0, node_child_process_1.execSync)(`taskkill /F /T /PID ${uiProcess.pid}`, { stdio: 'ignore' });
579
+ }
580
+ else {
581
+ uiProcess.kill('SIGTERM');
582
+ }
583
+ }
584
+ }
585
+ catch { }
586
+ await new Promise(resolve => setTimeout(resolve, 1000));
587
+ process.exit(0);
588
+ };
589
+ process.on('SIGINT', cleanup);
590
+ process.on('SIGTERM', cleanup);
591
+ process.on('exit', cleanup);
592
+ // Keep process alive to listen for signals
593
+ await new Promise(() => { });
594
+ }
595
+ /** Run tsc --noEmit in the frontend directory to surface TypeScript errors before the dev server starts. */
596
+ async typeCheckFrontend(frontendPath) {
597
+ // Skip if no tsconfig.json — project may not use TypeScript
598
+ const tsconfig = node_path_1.default.join(frontendPath, 'tsconfig.json');
599
+ if (!(await fs_extra_1.default.pathExists(tsconfig))) {
600
+ this.recordStep('Frontend type check', 'skipped', 'no tsconfig.json');
601
+ return;
602
+ }
603
+ // Prefer a non-interactive local invocation of tsc to avoid npx network installs/prompts
604
+ const localTsc = node_path_1.default.join(frontendPath, 'node_modules', '.bin', process.platform === 'win32' ? 'tsc.cmd' : 'tsc');
605
+ if (!(await fs_extra_1.default.pathExists(localTsc))) {
606
+ this.recordStep('Frontend type check', 'skipped', 'TypeScript tooling not found in frontend node_modules');
607
+ this.warn('Skipping frontend type check: local TypeScript compiler not found (install dev dependencies to enable this check).');
608
+ return;
609
+ }
610
+ this.vlog('šŸ” Type-checking frontend...');
611
+ try {
612
+ const output = (0, node_child_process_1.execSync)(`"${localTsc}" --noEmit`, { stdio: 'pipe', cwd: frontendPath });
613
+ if (this.verbose && output.toString().trim()) {
614
+ process.stdout.write(output.toString());
615
+ }
616
+ this.recordStep('Frontend type check', 'success');
617
+ }
618
+ catch (error) {
619
+ const detail = this.extractBuildOutput(error);
620
+ this.recordStep('Frontend type check', 'warning', 'TypeScript errors detected');
621
+ // Show errors but continue — dev server still runs with transpile-only mode
622
+ if (detail) {
623
+ this.warn(`Frontend TypeScript errors:${detail}`);
624
+ }
625
+ }
626
+ }
627
+ /** Resolves when the process emits output matching `pattern`, or after `timeoutMs` as a fallback. */
628
+ waitForReady(proc, pattern, timeoutMs) {
629
+ return new Promise((resolve, reject) => {
630
+ let done = false;
631
+ let timeout;
632
+ const cleanup = () => {
633
+ if (timeout) {
634
+ clearTimeout(timeout);
635
+ timeout = undefined;
636
+ }
637
+ proc.stdout?.off('data', onData);
638
+ proc.stderr?.off('data', onData);
639
+ proc.off('exit', onExit);
640
+ proc.off('close', onClose);
641
+ proc.off('error', onError);
642
+ };
643
+ const finishResolve = () => {
644
+ if (done)
645
+ return;
646
+ done = true;
647
+ cleanup();
648
+ resolve();
649
+ };
650
+ const finishReject = (error) => {
651
+ if (done)
652
+ return;
653
+ done = true;
654
+ cleanup();
655
+ reject(error);
656
+ };
657
+ const onData = (chunk) => {
658
+ if (pattern.test(chunk.toString())) {
659
+ finishResolve();
660
+ }
661
+ };
662
+ const onExit = (code) => {
663
+ if (!done) {
664
+ finishReject(new Error(`Process exited before matching readiness pattern (code ${code ?? 'null'})`));
665
+ }
666
+ };
667
+ const onClose = (code) => {
668
+ if (!done) {
669
+ finishReject(new Error(`Process closed before matching readiness pattern (code ${code ?? 'null'})`));
670
+ }
671
+ };
672
+ const onError = (error) => {
673
+ if (!done) {
674
+ finishReject(error);
675
+ }
676
+ };
677
+ proc.stdout?.on('data', onData);
678
+ proc.stderr?.on('data', onData);
679
+ proc.on('exit', onExit);
680
+ proc.on('close', onClose);
681
+ proc.on('error', onError);
682
+ timeout = setTimeout(() => {
683
+ // Preserve previous behavior: resolve after timeout as a fallback.
684
+ finishResolve();
685
+ }, timeoutMs);
686
+ });
259
687
  }
260
688
  async loadDataSources() {
261
689
  const dataSources = [];
262
- const dataSourcesPath = node_path_1.default.join(process.cwd(), 'src', 'dataSources');
690
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
691
+ const dataSourcesPath = node_path_1.default.join(backendPath, 'src', 'dataSources');
263
692
  if (await fs_extra_1.default.pathExists(dataSourcesPath)) {
264
693
  const files = await fs_extra_1.default.readdir(dataSourcesPath);
265
694
  for (const file of files) {
@@ -281,32 +710,101 @@ class Run extends core_1.Command {
281
710
  }
282
711
  return dataSources;
283
712
  }
284
- async executeApp() {
285
- this.log('šŸš€ Starting Slingr application...');
286
- // Execute using the npm run dev script which is more reliable
713
+ async runUIOnly(port) {
714
+ const uiPort = port || 8000;
715
+ // Check for frontend directory
716
+ const frontendPath = node_path_1.default.join(process.cwd(), 'frontend');
717
+ if (!(await fs_extra_1.default.pathExists(node_path_1.default.join(frontendPath, 'package.json')))) {
718
+ this.error('Frontend directory not found. Cannot run UI-only mode.\n\n' + 'Expected frontend directory at: ' + frontendPath);
719
+ }
720
+ await (0, viewsGenerator_js_1.generateViewsContext)(process.cwd(), this.verbose);
721
+ this.log('šŸŽØ Starting UI development server...');
722
+ this.log(`Starting UI dev server on port ${uiPort}...`);
723
+ this.log('');
287
724
  try {
288
- this.log('');
289
725
  (0, node_child_process_1.execSync)('npm run dev', {
290
726
  stdio: 'inherit',
291
- cwd: process.cwd(),
727
+ cwd: frontendPath,
728
+ env: { ...process.env, PORT: uiPort.toString() },
292
729
  });
293
730
  }
294
731
  catch (error) {
295
- // If it's Ctrl+C, exit gracefully
296
732
  if (error.signal === 'SIGINT') {
297
- this.log('\nšŸ‘‹ Application stopped gracefully');
733
+ this.log('\nšŸ‘‹ UI server stopped gracefully');
298
734
  process.exit(0);
299
735
  }
300
- // If npm run dev failed, try with the compiled version
301
- this.log('šŸ”„ npm run dev failed, trying with compiled version...');
736
+ this.error(`Failed to start UI server: ${error.message}`);
737
+ }
738
+ }
739
+ async runBackendOnly(flags) {
740
+ this.vlog('āš™ļø Starting backend server only...');
741
+ await this.buildFramework();
742
+ await this.generateCode();
743
+ if (!flags['skip-infra']) {
744
+ await this.checkInfrastructure();
745
+ }
746
+ else {
747
+ this.recordStep('Infrastructure', 'skipped', '--skip-infra');
748
+ }
749
+ this.printSummary();
750
+ await this.executeApp(flags.port);
751
+ }
752
+ async executeApp(port, extraEnv = {}, production = false) {
753
+ this.vlog('šŸš€ Starting Slingr application...');
754
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
755
+ const env = { ...process.env, ...extraEnv };
756
+ env.LOG_LEVEL = this.verbose ? 'debug' : process.env.LOG_LEVEL || 'info';
757
+ if (port) {
758
+ env.PORT = port.toString();
759
+ this.vlog(`Using port: ${port}`);
760
+ }
761
+ if (production) {
762
+ // Production mode: run compiled version
302
763
  try {
303
- (0, node_child_process_1.execSync)('node dist/index.js', {
764
+ this.vlog('');
765
+ const entryJs = 'dist/src/App.js';
766
+ const entryJsPath = node_path_1.default.join(backendPath, entryJs);
767
+ if (!(await fs_extra_1.default.pathExists(entryJsPath))) {
768
+ this.error('Compiled backend not found at: ' +
769
+ entryJsPath +
770
+ '\n\n' +
771
+ 'Please ensure the backend was compiled successfully.');
772
+ }
773
+ (0, node_child_process_1.execSync)(`node ${entryJs}`, {
304
774
  stdio: 'inherit',
305
- cwd: process.cwd(),
775
+ cwd: backendPath,
776
+ env,
306
777
  });
307
778
  }
308
- catch (nodeError) {
309
- this.error(`Failed to start application: ${nodeError.message}`);
779
+ catch (error) {
780
+ // If it's Ctrl+C, exit gracefully
781
+ if (error.signal === 'SIGINT') {
782
+ this.log('\nšŸ‘‹ Application stopped gracefully');
783
+ process.exit(0);
784
+ }
785
+ this.error(`Failed to start application: ${error.message}`);
786
+ }
787
+ }
788
+ else {
789
+ // Development mode: use ts-node src/App.ts or npm run dev
790
+ try {
791
+ this.vlog('');
792
+ const devCmd = 'npx ts-node src/App.ts';
793
+ this.vlog(`Running: ${devCmd} (cwd: ${backendPath})`);
794
+ (0, node_child_process_1.execSync)(devCmd, {
795
+ stdio: 'inherit',
796
+ cwd: backendPath,
797
+ env,
798
+ });
799
+ }
800
+ catch (error) {
801
+ // If it's Ctrl+C, exit gracefully
802
+ if (error.signal === 'SIGINT') {
803
+ this.log('\nšŸ‘‹ Application stopped gracefully');
804
+ process.exit(0);
805
+ }
806
+ // In dev mode, fail if startup fails - don't fall back to production
807
+ this.error(`Failed to start application in development mode: ${error.message}`);
310
808
  }
311
809
  }
312
810
  }