@slingr/cli 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +490 -319
  3. package/bin/dev.cmd +2 -2
  4. package/bin/dev.js +5 -5
  5. package/bin/run.cmd +2 -2
  6. package/bin/run.js +4 -4
  7. package/bin/slingr +1 -0
  8. package/dist/commands/build.d.ts +20 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +206 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/create-app.d.ts +0 -1
  13. package/dist/commands/create-app.d.ts.map +1 -1
  14. package/dist/commands/create-app.js +38 -57
  15. package/dist/commands/create-app.js.map +1 -1
  16. package/dist/commands/debug.d.ts +28 -0
  17. package/dist/commands/debug.d.ts.map +1 -0
  18. package/dist/commands/debug.js +474 -0
  19. package/dist/commands/debug.js.map +1 -0
  20. package/dist/commands/ds.d.ts +14 -1
  21. package/dist/commands/ds.d.ts.map +1 -1
  22. package/dist/commands/ds.js +450 -121
  23. package/dist/commands/ds.js.map +1 -1
  24. package/dist/commands/gql.d.ts +1 -1
  25. package/dist/commands/gql.d.ts.map +1 -1
  26. package/dist/commands/gql.js +190 -184
  27. package/dist/commands/gql.js.map +1 -1
  28. package/dist/commands/infra/down.d.ts.map +1 -1
  29. package/dist/commands/infra/down.js +8 -7
  30. package/dist/commands/infra/down.js.map +1 -1
  31. package/dist/commands/infra/up.d.ts.map +1 -1
  32. package/dist/commands/infra/up.js +8 -7
  33. package/dist/commands/infra/up.js.map +1 -1
  34. package/dist/commands/infra/update.d.ts +1 -0
  35. package/dist/commands/infra/update.d.ts.map +1 -1
  36. package/dist/commands/infra/update.js +33 -69
  37. package/dist/commands/infra/update.js.map +1 -1
  38. package/dist/commands/run.d.ts +29 -2
  39. package/dist/commands/run.d.ts.map +1 -1
  40. package/dist/commands/run.js +628 -130
  41. package/dist/commands/run.js.map +1 -1
  42. package/dist/commands/setup.d.ts +1 -1
  43. package/dist/commands/setup.d.ts.map +1 -1
  44. package/dist/commands/setup.js +34 -71
  45. package/dist/commands/setup.js.map +1 -1
  46. package/dist/commands/sync-metadata.d.ts +15 -0
  47. package/dist/commands/sync-metadata.d.ts.map +1 -0
  48. package/dist/commands/sync-metadata.js +225 -0
  49. package/dist/commands/sync-metadata.js.map +1 -0
  50. package/dist/commands/users.d.ts +30 -0
  51. package/dist/commands/users.d.ts.map +1 -0
  52. package/dist/commands/users.js +472 -0
  53. package/dist/commands/users.js.map +1 -0
  54. package/dist/commands/views.d.ts +11 -0
  55. package/dist/commands/views.d.ts.map +1 -0
  56. package/dist/commands/views.js +73 -0
  57. package/dist/commands/views.js.map +1 -0
  58. package/dist/projectStructure.d.ts +2 -2
  59. package/dist/projectStructure.d.ts.map +1 -1
  60. package/dist/projectStructure.js +281 -69
  61. package/dist/projectStructure.js.map +1 -1
  62. package/dist/scripts/generate-metadata.d.ts +13 -0
  63. package/dist/scripts/generate-metadata.d.ts.map +1 -0
  64. package/dist/scripts/generate-metadata.js +412 -0
  65. package/dist/scripts/generate-metadata.js.map +1 -0
  66. package/dist/scripts/generate-metadata.ts +498 -0
  67. package/dist/scripts/generate-schema.d.ts +1 -1
  68. package/dist/scripts/generate-schema.js +168 -74
  69. package/dist/scripts/generate-schema.js.map +1 -1
  70. package/dist/scripts/generate-schema.ts +258 -143
  71. package/dist/templates/.env.template +23 -0
  72. package/dist/templates/.firebaserc.template +5 -0
  73. package/dist/templates/.github/copilot-instructions.md.template +652 -17
  74. package/dist/templates/backend/Dockerfile.template +30 -0
  75. package/dist/templates/config/datasource.ts.template +12 -9
  76. package/dist/templates/config/jest.config.ts +30 -30
  77. package/dist/templates/config/jest.setup.ts +1 -1
  78. package/dist/templates/config/tsconfig.json.template +50 -29
  79. package/dist/templates/dataSources/mysql.ts.template +16 -13
  80. package/dist/templates/dataSources/postgres.ts.template +15 -13
  81. package/dist/templates/dataset-generator-script.ts.template +139 -139
  82. package/dist/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  83. package/dist/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  84. package/dist/templates/datasets/mysql-default/App.jsonl.template +4 -4
  85. package/dist/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  86. package/dist/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  87. package/dist/templates/datasets/mysql-default/User.jsonl.template +1 -0
  88. package/dist/templates/datasets/mysql-default/instructions.md.template +1 -0
  89. package/dist/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  90. package/dist/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  91. package/dist/templates/datasets/postgres-default/App.jsonl.template +4 -4
  92. package/dist/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  93. package/dist/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  94. package/dist/templates/datasets/postgres-default/User.jsonl.template +1 -0
  95. package/dist/templates/datasets/postgres-default/instructions.md.template +1 -0
  96. package/dist/templates/docker-compose.prod-test.yml.template +32 -0
  97. package/dist/templates/docker-compose.yml.template +24 -0
  98. package/dist/templates/docs/app-description.md.template +33 -33
  99. package/dist/templates/firebase.json.template +68 -0
  100. package/dist/templates/frontend/.umirc.ts.template +23 -0
  101. package/dist/templates/frontend/package.json.template +45 -0
  102. package/dist/templates/frontend/public/config.json +6 -0
  103. package/dist/templates/frontend/public/logo.svg +6 -0
  104. package/dist/templates/frontend/src/app.tsx.template +44 -0
  105. package/dist/templates/frontend/src/global.less.template +117 -0
  106. package/dist/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  107. package/dist/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  108. package/dist/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  109. package/dist/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  110. package/dist/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  111. package/dist/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  112. package/dist/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  113. package/dist/templates/frontend/tsconfig.json.template +50 -0
  114. package/dist/templates/gql/codegen.yml.template +25 -25
  115. package/dist/templates/gql/index.ts.template +17 -24
  116. package/dist/templates/gql/operations.graphql.template +30 -30
  117. package/dist/templates/ops/README.md.template +1045 -0
  118. package/dist/templates/ops/cloudbuild.yaml.template +161 -0
  119. package/dist/templates/ops/scripts/_utils.js.template +217 -0
  120. package/dist/templates/ops/scripts/deploy.js.template +145 -0
  121. package/dist/templates/ops/scripts/setup-gcp.js.template +330 -0
  122. package/dist/templates/ops/scripts/setup-secrets.js.template +76 -0
  123. package/dist/templates/ops/scripts/test-prod-local.js.template +49 -0
  124. package/dist/templates/package.json.template +50 -38
  125. package/dist/templates/pnpm-workspace.yaml.template +3 -0
  126. package/dist/templates/prompt-analysis.md.template +110 -110
  127. package/dist/templates/prompt-script-generation.md.template +258 -258
  128. package/dist/templates/src/Address.ts.template +28 -31
  129. package/dist/templates/src/App.ts.template +17 -61
  130. package/dist/templates/src/Company.ts.template +41 -47
  131. package/dist/templates/src/Models.test.ts.template +654 -654
  132. package/dist/templates/src/Person.test.ts.template +289 -289
  133. package/dist/templates/src/Person.ts.template +90 -105
  134. package/dist/templates/src/actions/index.ts.template +11 -11
  135. package/dist/templates/src/auth/permissions.ts.template +34 -0
  136. package/dist/templates/src/data/App.ts.template +48 -0
  137. package/dist/templates/src/data/User.ts.template +35 -0
  138. package/dist/templates/src/types/gql.d.ts.template +17 -17
  139. package/dist/templates/vscode/extensions.json +4 -3
  140. package/dist/templates/vscode/settings.json +17 -11
  141. package/dist/templates/workspace-package.json.template +21 -0
  142. package/dist/utils/buildCache.d.ts +12 -0
  143. package/dist/utils/buildCache.d.ts.map +1 -0
  144. package/dist/utils/buildCache.js +102 -0
  145. package/dist/utils/buildCache.js.map +1 -0
  146. package/dist/utils/checkFramework.d.ts +27 -0
  147. package/dist/utils/checkFramework.d.ts.map +1 -0
  148. package/dist/utils/checkFramework.js +104 -0
  149. package/dist/utils/checkFramework.js.map +1 -0
  150. package/dist/utils/datasourceParser.d.ts +11 -0
  151. package/dist/utils/datasourceParser.d.ts.map +1 -1
  152. package/dist/utils/datasourceParser.js +154 -56
  153. package/dist/utils/datasourceParser.js.map +1 -1
  154. package/dist/utils/dockerManager.d.ts +25 -0
  155. package/dist/utils/dockerManager.d.ts.map +1 -0
  156. package/dist/utils/dockerManager.js +281 -0
  157. package/dist/utils/dockerManager.js.map +1 -0
  158. package/dist/utils/infraFileParser.d.ts +26 -0
  159. package/dist/utils/infraFileParser.d.ts.map +1 -0
  160. package/dist/utils/infraFileParser.js +75 -0
  161. package/dist/utils/infraFileParser.js.map +1 -0
  162. package/dist/utils/jsonlLoader.d.ts +91 -12
  163. package/dist/utils/jsonlLoader.d.ts.map +1 -1
  164. package/dist/utils/jsonlLoader.js +674 -63
  165. package/dist/utils/jsonlLoader.js.map +1 -1
  166. package/dist/utils/model-analyzer.d.ts.map +1 -1
  167. package/dist/utils/model-analyzer.js +67 -13
  168. package/dist/utils/model-analyzer.js.map +1 -1
  169. package/dist/utils/userManagement.d.ts +57 -0
  170. package/dist/utils/userManagement.d.ts.map +1 -0
  171. package/dist/utils/userManagement.js +288 -0
  172. package/dist/utils/userManagement.js.map +1 -0
  173. package/dist/utils/viewsGenerator.d.ts +15 -0
  174. package/dist/utils/viewsGenerator.d.ts.map +1 -0
  175. package/dist/utils/viewsGenerator.js +311 -0
  176. package/dist/utils/viewsGenerator.js.map +1 -0
  177. package/oclif.manifest.json +445 -20
  178. package/package.json +29 -26
  179. package/src/templates/.env.template +23 -0
  180. package/src/templates/.firebaserc.template +5 -0
  181. package/src/templates/.github/copilot-instructions.md.template +652 -17
  182. package/src/templates/backend/Dockerfile.template +30 -0
  183. package/src/templates/config/datasource.ts.template +12 -9
  184. package/src/templates/config/jest.config.ts +30 -30
  185. package/src/templates/config/jest.setup.ts +1 -1
  186. package/src/templates/config/tsconfig.json.template +50 -29
  187. package/src/templates/dataSources/mysql.ts.template +16 -13
  188. package/src/templates/dataSources/postgres.ts.template +15 -13
  189. package/src/templates/dataset-generator-script.ts.template +139 -139
  190. package/src/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  191. package/src/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  192. package/src/templates/datasets/mysql-default/App.jsonl.template +4 -4
  193. package/src/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  194. package/src/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  195. package/src/templates/datasets/mysql-default/User.jsonl.template +1 -0
  196. package/src/templates/datasets/mysql-default/instructions.md.template +1 -0
  197. package/src/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  198. package/src/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  199. package/src/templates/datasets/postgres-default/App.jsonl.template +4 -4
  200. package/src/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  201. package/src/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  202. package/src/templates/datasets/postgres-default/User.jsonl.template +1 -0
  203. package/src/templates/datasets/postgres-default/instructions.md.template +1 -0
  204. package/src/templates/docker-compose.prod-test.yml.template +32 -0
  205. package/src/templates/docker-compose.yml.template +24 -0
  206. package/src/templates/docs/app-description.md.template +33 -33
  207. package/src/templates/firebase.json.template +68 -0
  208. package/src/templates/frontend/.umirc.ts.template +23 -0
  209. package/src/templates/frontend/package.json.template +45 -0
  210. package/src/templates/frontend/public/config.json +6 -0
  211. package/src/templates/frontend/public/logo.svg +6 -0
  212. package/src/templates/frontend/src/app.tsx.template +44 -0
  213. package/src/templates/frontend/src/global.less.template +117 -0
  214. package/src/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  215. package/src/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  216. package/src/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  217. package/src/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  218. package/src/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  219. package/src/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  220. package/src/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  221. package/src/templates/frontend/tsconfig.json.template +50 -0
  222. package/src/templates/gql/codegen.yml.template +25 -25
  223. package/src/templates/gql/index.ts.template +17 -24
  224. package/src/templates/gql/operations.graphql.template +30 -30
  225. package/src/templates/ops/README.md.template +1045 -0
  226. package/src/templates/ops/cloudbuild.yaml.template +161 -0
  227. package/src/templates/ops/scripts/_utils.js.template +217 -0
  228. package/src/templates/ops/scripts/deploy.js.template +145 -0
  229. package/src/templates/ops/scripts/setup-gcp.js.template +330 -0
  230. package/src/templates/ops/scripts/setup-secrets.js.template +76 -0
  231. package/src/templates/ops/scripts/test-prod-local.js.template +49 -0
  232. package/src/templates/package.json.template +50 -38
  233. package/src/templates/pnpm-workspace.yaml.template +3 -0
  234. package/src/templates/prompt-analysis.md.template +110 -110
  235. package/src/templates/prompt-script-generation.md.template +258 -258
  236. package/src/templates/src/Address.ts.template +28 -31
  237. package/src/templates/src/App.ts.template +17 -61
  238. package/src/templates/src/Company.ts.template +41 -47
  239. package/src/templates/src/Models.test.ts.template +654 -654
  240. package/src/templates/src/Person.test.ts.template +289 -289
  241. package/src/templates/src/Person.ts.template +90 -105
  242. package/src/templates/src/actions/index.ts.template +11 -11
  243. package/src/templates/src/auth/permissions.ts.template +34 -0
  244. package/src/templates/src/data/App.ts.template +48 -0
  245. package/src/templates/src/data/User.ts.template +35 -0
  246. package/src/templates/src/types/gql.d.ts.template +17 -17
  247. package/src/templates/vscode/extensions.json +4 -3
  248. package/src/templates/vscode/settings.json +17 -11
  249. package/src/templates/workspace-package.json.template +21 -0
  250. package/dist/templates/src/index.ts +0 -66
  251. package/src/templates/src/index.ts +0 -66
@@ -42,13 +42,21 @@ const node_path_1 = __importDefault(require("node:path"));
42
42
  const child_process_1 = require("child_process");
43
43
  const yaml = __importStar(require("js-yaml"));
44
44
  const jsonlLoader_js_1 = require("../utils/jsonlLoader.js");
45
+ const checkFramework_js_1 = require("../utils/checkFramework.js");
46
+ const datasourceParser_js_1 = require("../utils/datasourceParser.js");
47
+ const infraFileParser_js_1 = require("../utils/infraFileParser.js");
45
48
  class Ds extends core_1.Command {
46
- static description = 'Manage datasets for datasources';
49
+ static description = 'Manage datasets and inspect datasources';
47
50
  static examples = [
48
- 'slingr ds postgres load',
49
- 'slingr ds postgres load custom-dataset',
50
- 'slingr ds mysql load default',
51
- 'slingr ds postgres reset',
51
+ '<%= config.bin %> <%= command.id %> mainDs load',
52
+ '<%= config.bin %> <%= command.id %> postgres load custom-dataset',
53
+ '<%= config.bin %> <%= command.id %> mainDs load --includeModels=User,Project',
54
+ '<%= config.bin %> <%= command.id %> mainDs load --includeModels="User,Project"',
55
+ '<%= config.bin %> <%= command.id %> mainDs load --excludeModels=TestData',
56
+ '<%= config.bin %> <%= command.id %> mainDs load --verbose',
57
+ '<%= config.bin %> <%= command.id %> postgres reset',
58
+ '<%= config.bin %> <%= command.id %> mainDs indexes',
59
+ '<%= config.bin %> <%= command.id %> mainDs indexes --verbose',
52
60
  ];
53
61
  static args = {
54
62
  datasource: core_1.Args.string({
@@ -56,8 +64,8 @@ class Ds extends core_1.Command {
56
64
  required: true,
57
65
  }),
58
66
  action: core_1.Args.string({
59
- description: 'Action to perform (load or reset)',
60
- options: ['load', 'reset'],
67
+ description: 'Action to perform (load, reset, or indexes)',
68
+ options: ['load', 'reset', 'indexes'],
61
69
  required: true,
62
70
  }),
63
71
  dataset: core_1.Args.string({
@@ -66,58 +74,88 @@ class Ds extends core_1.Command {
66
74
  default: 'default',
67
75
  }),
68
76
  };
77
+ static flags = {
78
+ includeModels: core_1.Flags.string({
79
+ description: 'Comma-separated list of model names to load. Only these models will be loaded (overrides datasetOptions.json). Use --includeModels=Model1,Model2 or --includeModels "Model1,Model2" syntax.',
80
+ required: false,
81
+ }),
82
+ excludeModels: core_1.Flags.string({
83
+ description: 'Comma-separated list of model names to skip. All other models will be loaded (overrides datasetOptions.json). Use --excludeModels=Model1,Model2 or --excludeModels "Model1,Model2" syntax.',
84
+ required: false,
85
+ }),
86
+ verbose: core_1.Flags.boolean({
87
+ description: 'Show detailed logs during dataset loading. Without this flag, only success and validation error messages are displayed.',
88
+ required: false,
89
+ default: false,
90
+ char: 'v',
91
+ }),
92
+ };
69
93
  async run() {
70
- const { args } = await this.parse(Ds);
94
+ const { args, flags } = await this.parse(Ds);
71
95
  const { datasource, action, dataset } = args;
96
+ // Reject conflicting filter flags early — before any app/Docker checks
97
+ if (flags['includeModels'] && flags['excludeModels']) {
98
+ this.error('--includeModels and --excludeModels are mutually exclusive. Provide only one to avoid ambiguous behaviour.');
99
+ }
72
100
  // Verify we're in a Slingr app directory
73
101
  const pkgPath = node_path_1.default.join(process.cwd(), 'package.json');
74
102
  if (!(await fs_extra_1.default.pathExists(pkgPath))) {
75
103
  this.error("Not in a Slingr application directory. Please run this command from your app's root directory.");
76
104
  }
77
- // Check if the datasource exists
78
- const dsPath = node_path_1.default.join(process.cwd(), 'src', 'dataSources', `${datasource}.ts`);
79
- if (!(await fs_extra_1.default.pathExists(dsPath))) {
80
- this.error(`Datasource ${datasource} not found. Expected file at: ${dsPath}`);
105
+ // Verify the datasource file exists (no auto-discovery)
106
+ try {
107
+ await (0, datasourceParser_js_1.discoverDatasourceFile)(datasource);
108
+ }
109
+ catch (error) {
110
+ this.error(error.message);
81
111
  }
82
112
  try {
83
113
  switch (action) {
84
114
  case 'load':
85
115
  // Prepare the environment before loading
86
- await this.prepareEnvironment();
87
- await this.loadDataset(datasource, dataset);
116
+ await this.prepareEnvironment(flags['verbose']);
117
+ await this.loadDataset(datasource, dataset, flags['includeModels'], flags['excludeModels'], flags['verbose']);
88
118
  break;
89
119
  case 'reset':
90
120
  await this.resetDatasource(datasource);
91
121
  break;
122
+ case 'indexes':
123
+ await this.showIndexes(datasource, flags['verbose']);
124
+ break;
92
125
  default:
93
126
  this.error(`Unknown action: ${action}`);
94
127
  }
95
128
  }
96
129
  catch (error) {
97
- this.error(`Failed to ${action} dataset: ${error.message}`);
130
+ this.error(`Failed to ${action}: ${error.message}`);
98
131
  }
99
132
  }
100
- async generateCode() {
133
+ async generateCode(verbose = false) {
101
134
  // Compile TypeScript code
102
- this.log('Compiling TypeScript code...');
103
- (0, child_process_1.execSync)('npm run build', { stdio: 'inherit' });
135
+ if (verbose) {
136
+ this.log('Compiling TypeScript code...');
137
+ }
138
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
139
+ // Clean compiled backend output to remove stale files from deleted models
140
+ await fs_extra_1.default.remove(node_path_1.default.join(backendPath, 'dist', 'src'));
141
+ await fs_extra_1.default.remove(node_path_1.default.join(backendPath, 'dist', 'generated'));
142
+ (0, child_process_1.execSync)('npm run build', { stdio: verbose ? 'inherit' : 'pipe', cwd: backendPath });
104
143
  }
105
- async ensureDatabaseDependencies(datasource) {
106
- // Get the datasource type by reading the datasource file
107
- const dsPath = node_path_1.default.join(process.cwd(), 'src', 'dataSources', `${datasource}.ts`);
144
+ async ensureDatabaseDependencies(datasource, verbose = false) {
145
+ // Get the datasource type by reading and parsing the datasource file
146
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
147
+ const dsPath = node_path_1.default.join(backendPath, 'src', 'dataSources', `${datasource}.ts`);
108
148
  const content = await fs_extra_1.default.readFile(dsPath, 'utf-8');
109
- // Extract database type from the configuration
110
- const typeMatch = content.match(/type:\s*['"]([^'"]+)['"]/);
111
- if (!typeMatch) {
112
- this.log('Could not determine database type from datasource configuration');
149
+ const parsedConfig = (0, infraFileParser_js_1.parseDataSourceFile)(content, datasource);
150
+ if (!parsedConfig) {
151
+ if (verbose) {
152
+ this.log('Could not determine database type from datasource configuration');
153
+ }
113
154
  return;
114
155
  }
115
- let dbType = typeMatch[1].toLowerCase();
116
- if (dbType === 'postgresql') {
117
- dbType = 'postgres';
118
- }
156
+ const dbType = parsedConfig.type;
119
157
  // Check package.json for required dependencies
120
- const packageJsonPath = node_path_1.default.join(process.cwd(), 'package.json');
158
+ const packageJsonPath = node_path_1.default.join(backendPath, 'package.json');
121
159
  const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
122
160
  const dependencies = {
123
161
  ...packageJson.dependencies,
@@ -139,93 +177,139 @@ class Ds extends core_1.Command {
139
177
  }
140
178
  // Install the required package if missing
141
179
  if (packageToInstall) {
142
- this.log(`Installing required database driver: ${packageToInstall}`);
180
+ if (verbose) {
181
+ this.log(`Installing required database driver: ${packageToInstall}`);
182
+ }
143
183
  try {
144
184
  (0, child_process_1.execSync)(`npm install ${packageToInstall}`, {
145
- stdio: 'inherit',
146
- cwd: process.cwd(),
185
+ stdio: verbose ? 'inherit' : 'pipe',
186
+ cwd: backendPath,
147
187
  });
148
- this.log(`Successfully installed ${packageToInstall}`);
188
+ if (verbose) {
189
+ this.log(`Successfully installed ${packageToInstall}`);
190
+ }
149
191
  }
150
192
  catch (error) {
151
193
  this.error(`Failed to install ${packageToInstall}: ${error.message}`);
152
194
  }
153
195
  }
154
196
  else {
155
- this.log(`Database driver for ${dbType} is already installed`);
197
+ if (verbose) {
198
+ this.log(`Database driver for ${dbType} is already installed`);
199
+ }
156
200
  }
157
201
  }
158
- async prepareEnvironment() {
159
- // Check if we have package.json with slingr-framework dependency
160
- const packageJsonPath = node_path_1.default.join(process.cwd(), 'package.json');
161
- const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
162
- if (!packageJson.dependencies?.['slingr-framework']) {
202
+ async prepareEnvironment(verbose = false) {
203
+ // Check if we have package.json with @slingr/framework-backend dependency
204
+ if (!(await (0, checkFramework_js_1.hasSlingrFramework)())) {
163
205
  this.error('This directory does not contain a Slingr application.');
164
206
  }
165
- // Check if node_modules exists, if not tell user to install dependencies
166
- const nodeModulesPath = node_path_1.default.join(process.cwd(), 'node_modules');
167
- if (!(await fs_extra_1.default.pathExists(nodeModulesPath))) {
168
- this.error('Dependencies not found. Please run "npm install" first to install the required dependencies.');
169
- }
170
- // Check if slingr-framework is specifically installed
171
- const frameworkPath = node_path_1.default.join(nodeModulesPath, 'slingr-framework');
172
- if (!(await fs_extra_1.default.pathExists(frameworkPath))) {
173
- this.error('slingr-framework not found in node_modules. Please run "npm install" to install dependencies.');
207
+ // Check if @slingr/framework-backend is installed (handles both workspace and non-workspace scenarios)
208
+ const frameworkPath = await (0, checkFramework_js_1.getFrameworkPath)();
209
+ if (!frameworkPath) {
210
+ this.error('@slingr/framework-backend not found. Please run "npm install" to install dependencies.');
174
211
  }
175
212
  // Generate code
176
- await this.generateCode();
213
+ await this.generateCode(verbose);
177
214
  }
178
- async loadDataset(datasource, dataset) {
179
- this.log(`Loading dataset '${dataset}' into datasource '${datasource}'...`);
215
+ async loadDataset(datasource, dataset, includeModelsFlag, excludeModelsFlag, verbose = false) {
216
+ if (verbose) {
217
+ this.log(`Loading dataset '${dataset}' into datasource '${datasource}'...`);
218
+ }
219
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
180
220
  // Check if the dataset directory exists using the convention: dataSourceName-datasetName
181
- const datasetPath = node_path_1.default.join(process.cwd(), 'src', 'datasets', `${datasource}-${dataset}`);
221
+ const datasetPath = node_path_1.default.join(backendPath, 'src', 'datasets', `${datasource}-${dataset}`);
182
222
  if (!(await fs_extra_1.default.pathExists(datasetPath))) {
183
223
  this.error(`Dataset not found at: ${datasetPath}`);
184
224
  }
225
+ // Normalize a list of model names: strip optional .jsonl extension users may have included
226
+ const normalizeModelNames = (names) => names.map(s => s.trim().replace(/\.jsonl$/i, '')).filter(Boolean);
227
+ // Split a string by comma or space (handles both --flag=A,B and PowerShell's --flag A,B → "A B")
228
+ const splitModelNames = (str) => {
229
+ // First try splitting by comma
230
+ const parts = str
231
+ .split(',')
232
+ .map(s => s.trim())
233
+ .filter(Boolean);
234
+ // If no commas found (single element), try splitting by space
235
+ // This handles PowerShell's array literal interpretation: --flag A,B → "A B"
236
+ if (parts.length === 1 && parts[0].includes(' ')) {
237
+ return parts[0].split(/\s+/).filter(Boolean);
238
+ }
239
+ return parts;
240
+ };
241
+ // Read datasetOptions.json if it exists (provides defaults that CLI flags override)
242
+ let includeModels;
243
+ let excludeModels;
244
+ const optionsFilePath = node_path_1.default.join(datasetPath, 'datasetOptions.json');
245
+ if (await fs_extra_1.default.pathExists(optionsFilePath)) {
246
+ try {
247
+ const options = await fs_extra_1.default.readJSON(optionsFilePath);
248
+ if (Array.isArray(options.includeModels) && options.includeModels.length > 0) {
249
+ includeModels = normalizeModelNames(options.includeModels);
250
+ }
251
+ if (Array.isArray(options.excludeModels) && options.excludeModels.length > 0) {
252
+ excludeModels = normalizeModelNames(options.excludeModels);
253
+ }
254
+ if (verbose) {
255
+ this.log(`📋 Loaded dataset options from datasetOptions.json`);
256
+ }
257
+ }
258
+ catch {
259
+ this.warn('Could not parse datasetOptions.json — ignoring it.');
260
+ }
261
+ }
262
+ // CLI flags override options from the file
263
+ if (includeModelsFlag) {
264
+ includeModels = normalizeModelNames(splitModelNames(includeModelsFlag));
265
+ }
266
+ if (excludeModelsFlag) {
267
+ excludeModels = normalizeModelNames(splitModelNames(excludeModelsFlag));
268
+ }
269
+ // When both are set (regardless of source), excludeModels is redundant because the
270
+ // loader already honours includeModels first. Warn and clear to avoid confusing output.
271
+ if (includeModels && includeModels.length > 0 && excludeModels && excludeModels.length > 0) {
272
+ this.warn('Both includeModels and excludeModels are set. excludeModels will be ignored because includeModels takes priority.');
273
+ excludeModels = undefined;
274
+ }
275
+ if (verbose && includeModels && includeModels.length > 0) {
276
+ this.log(`🔍 Including only models: ${includeModels.join(', ')}`);
277
+ }
278
+ if (verbose && excludeModels && excludeModels.length > 0) {
279
+ this.log(`🚫 Excluding models: ${excludeModels.join(', ')}`);
280
+ }
281
+ // Validate Docker is running and database container is available (do this early)
282
+ await this.validateDockerInfrastructure(datasource, verbose);
283
+ // Ensure the required database dependencies are installed
284
+ await this.ensureDatabaseDependencies(datasource, verbose);
285
+ // Load the datasource configuration using the new parser
286
+ // This intelligently discovers whatever is exported from the datasource file
287
+ const dsConfig = await (0, datasourceParser_js_1.loadDataSourceFromFile)(datasource);
185
288
  // Initialize the JSONL loader
186
289
  const loader = new jsonlLoader_js_1.JsonlDatasetLoader();
187
290
  // Path to the compiled JavaScript files
188
- const distPath = node_path_1.default.join(process.cwd(), 'dist');
291
+ const distPath = node_path_1.default.join(backendPath, 'dist');
189
292
  // Auto-discover models from compiled JS files
190
- const modelMap = await (0, jsonlLoader_js_1.discoverModels)(distPath);
293
+ const modelMap = await (0, jsonlLoader_js_1.discoverModels)(distPath, verbose);
191
294
  if (Object.keys(modelMap).length === 0) {
192
- this.error('No models found. Please ensure your models are compiled (run npm run build) and extend BaseModel.');
193
- }
194
- this.log(`Discovered ${Object.keys(modelMap).length} models:`, Object.keys(modelMap));
195
- // Load dataset using the new JSONL loader
196
- const loadResults = await loader.loadDataset({
197
- datasetPath,
198
- modelMap,
199
- validateRecords: true,
200
- verbose: true,
201
- });
202
- if (loadResults.length === 0) {
203
- this.error('No compatible JSONL files found or no models matched the file names.');
204
- }
205
- // Import the datasource module from compiled JS
206
- const dsModulePath = node_path_1.default.join(process.cwd(), 'dist', 'dataSources', `${datasource}.js`);
207
- const dsModule = require(dsModulePath);
208
- // Try different possible export names
209
- let dsConfig = dsModule[`${datasource}`];
210
- if (!dsConfig) {
211
- dsConfig = dsModule[`mainDs`];
295
+ this.error('No models found. Please ensure your models are compiled (run npm run build) and extend BaseDataModel.');
212
296
  }
213
- if (!dsConfig) {
214
- const availableExports = Object.keys(dsModule);
215
- this.error(`Could not find datasource instance. Tried: '${datasource}', '${datasource}DataSource'. Available exports: ${availableExports.join(', ')} in ${dsModulePath}`);
297
+ if (verbose) {
298
+ this.log(`Discovered ${Object.keys(modelMap).length} models:`, Object.keys(modelMap));
216
299
  }
217
- // Validate Docker is running and PostgreSQL container is available
218
- await this.validateDockerInfrastructure(datasource);
219
- // Ensure the required database dependencies are installed
220
- await this.ensureDatabaseDependencies(datasource);
221
- // Initialize the datasource with its configuration
300
+ // Initialize the datasource after models are discovered so TypeORM metadata is registered
301
+ // This also enables loading missing references from DB during dataset validation
222
302
  try {
223
- this.log(`Attempting to connect to database: ${JSON.stringify(dsConfig.getOptions(), null, 2)}`);
303
+ if (verbose) {
304
+ this.log(`Attempting to connect to database: ${JSON.stringify(dsConfig.getOptions(), null, 2)}`);
305
+ }
224
306
  await dsConfig.initialize();
225
307
  }
226
308
  catch (error) {
227
309
  const errorMessage = error.message;
228
- this.log(`Connection failed with error: ${errorMessage}`);
310
+ if (verbose) {
311
+ this.log(`Connection failed with error: ${errorMessage}`);
312
+ }
229
313
  // Check if docker-compose.yml exists
230
314
  const dockerComposePath = node_path_1.default.join(process.cwd(), 'docker-compose.yml');
231
315
  const hasDockerCompose = await fs_extra_1.default.pathExists(dockerComposePath);
@@ -257,40 +341,62 @@ class Ds extends core_1.Command {
257
341
  this.error(`Cannot connect to database server. The database infrastructure exists but is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - docker-compose up -d (to start just the infrastructure services)`);
258
342
  }
259
343
  else {
260
- this.error(`Cannot connect to database server. The database infrastructure has not been set up yet.\n\nTo fix this, you have two options:\n\n1. Simple option:\n - slingr run (automatically generates infrastructure and starts the application)\n\n2. Step-by-step option:\n - slingr infra:update --all (to generate docker-compose.yml and infrastructure files)\n - Then run: slingr run OR docker-compose up -d`);
344
+ this.error(`Cannot connect to database server. No docker-compose.yml found.\n\nPlease ensure:\n 1. Database server is running\n 2. Connection details in ${datasource}.ts are correct`);
261
345
  }
262
346
  }
263
- // Check for connection refused errors
347
+ // Check for specific connection errors
264
348
  if (isConnectionRefused(error)) {
265
349
  if (hasDockerCompose) {
266
- this.error(`Cannot connect to database server. The database infrastructure exists but is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - docker-compose up -d (to start just the infrastructure services)`);
350
+ this.error(`Cannot connect to database server (connection refused). The database infrastructure exists but is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - docker-compose up -d (to start just the infrastructure services)`);
267
351
  }
268
352
  else {
269
- this.error(`Cannot connect to database server. The database infrastructure has not been set up yet.\n\nTo fix this, you have two options:\n\n1. Simple option:\n - slingr run (automatically generates infrastructure and starts the application)\n\n2. Step-by-step option:\n - slingr infra:update --all (to generate docker-compose.yml and infrastructure files)\n - Then run: slingr run OR docker-compose up -d`);
353
+ this.error(`Cannot connect to database server (connection refused). No docker-compose.yml found.\n\nPlease ensure:\n 1. Database server is running and accepting connections\n 2. Connection details (host/port) in ${datasource}.ts are correct`);
270
354
  }
271
355
  }
272
- // Check for database not found errors
273
- if (errorMessage.includes('database') && errorMessage.includes('does not exist')) {
274
- this.error(`Database infrastructure not found. The database server is not running or the database does not exist.\n\nTo fix this, run the following command:\n - slingr run (to start the full application with infrastructure)`);
275
- }
276
- // Re-throw other errors
277
- throw error;
356
+ // Generic fallback error
357
+ this.error(`Failed to initialize database: ${errorMessage}`);
358
+ }
359
+ // Load dataset using the new JSONL loader
360
+ // Pass the dataSource so missing references can be loaded from DB during validation
361
+ const loadResults = await loader.loadDataset({
362
+ datasetPath,
363
+ modelMap,
364
+ validateRecords: true,
365
+ verbose,
366
+ includeModels,
367
+ excludeModels,
368
+ dataSource: dsConfig,
369
+ });
370
+ if (loadResults.length === 0) {
371
+ this.error('No compatible JSONL files found or no models matched the file names.');
278
372
  }
279
373
  const dataSource = dsConfig.getTypeOrmDataSource();
280
374
  if (!dataSource || !dataSource.isInitialized) {
281
375
  this.error('DataSource failed to initialize');
282
376
  }
283
- this.log('DataSource initialized successfully');
377
+ if (verbose) {
378
+ this.log('DataSource initialized successfully');
379
+ }
284
380
  // Process each load result using TypeORM repositories (database-agnostic)
285
381
  try {
286
382
  // Use the new generic loader method
287
- await loader.loadDatasetToDatabase(loadResults, dsConfig, true);
383
+ await loader.loadDatasetToDatabase(loadResults, dsConfig, verbose);
288
384
  // Print summary
289
- const totalSuccess = loadResults.reduce((sum, r) => sum + r.successCount, 0);
290
- const totalErrors = loadResults.reduce((sum, r) => sum + r.errorCount, 0);
291
- this.log(`
292
- 🎉 Successfully loaded dataset '${dataset}' into datasource '${datasource}'`);
293
- this.log(`📈 Summary: ${totalSuccess} records loaded, ${totalErrors} errors`);
385
+ const totalLoaded = loadResults.reduce((sum, r) => sum + r.successCount, 0);
386
+ const totalValidationErrors = loadResults.reduce((sum, r) => sum + r.errorCount, 0);
387
+ const totalSaved = loadResults.reduce((sum, r) => sum + r.saveSuccessCount, 0);
388
+ const totalSaveErrors = loadResults.reduce((sum, r) => sum + r.saveErrorCount, 0);
389
+ this.log(`
390
+ 🎉 Dataset loading complete for '${dataset}' into datasource '${datasource}'`);
391
+ this.log(`
392
+ 📈 Summary:
393
+ • ${totalLoaded} records loaded from JSONL files
394
+ • ${totalValidationErrors} validation errors
395
+ • ${totalSaved} records successfully saved to database
396
+ • ${totalSaveErrors} save errors`);
397
+ if (totalSaveErrors > 0) {
398
+ this.log(`\n⚠️ Some records failed to save. Run with --verbose flag for detailed error information.`);
399
+ }
294
400
  }
295
401
  catch (error) {
296
402
  this.error(`Failed to load dataset: ${error.message}`);
@@ -299,16 +405,20 @@ class Ds extends core_1.Command {
299
405
  // Clean up by closing the datasource connection
300
406
  if (dataSource.isInitialized) {
301
407
  // Add delay to ensure all operations complete
302
- this.log('⏳ Finalizing database operations...');
408
+ if (verbose) {
409
+ this.log('⏳ Finalizing database operations...');
410
+ }
303
411
  await new Promise(resolve => setTimeout(resolve, 2000));
304
412
  await dataSource.destroy();
305
- this.log('🔌 Database connection closed');
413
+ if (verbose) {
414
+ this.log('🔌 Database connection closed');
415
+ }
306
416
  }
307
417
  }
308
418
  }
309
419
  async resetDatasource(datasource) {
310
420
  this.log(`Resetting datasource '${datasource}' infrastructure...`);
311
- this.ensureDockerIsRunning();
421
+ await this.ensureDockerIsRunning();
312
422
  const composeFile = node_path_1.default.join(process.cwd(), 'docker-compose.yml');
313
423
  if (!(await fs_extra_1.default.pathExists(composeFile))) {
314
424
  this.error(`Docker infrastructure not found. Missing docker-compose.yml file.\n\nTo fix this, run one of the following commands:\n - slingr infra:update --all (to generate infrastructure files)\n - slingr run (to automatically generate and start infrastructure)`);
@@ -370,12 +480,13 @@ class Ds extends core_1.Command {
370
480
  await this.validateDockerInfrastructure(datasource);
371
481
  this.log(`✅ Datasource '${datasource}' has been reset successfully.`);
372
482
  }
373
- ensureDockerIsRunning() {
483
+ async ensureDockerIsRunning() {
484
+ const { ensureDockerIsRunning } = await import('../utils/dockerManager.js');
374
485
  try {
375
- (0, child_process_1.execSync)('docker info', { stdio: 'pipe' });
486
+ await ensureDockerIsRunning('slingr ds');
376
487
  }
377
488
  catch (error) {
378
- this.error(`Docker is not running. Please start Docker Desktop before running dataset commands.\n\nThe dataset command requires Docker to run the PostgreSQL infrastructure.`);
489
+ this.error(error.message);
379
490
  }
380
491
  }
381
492
  getComposeCommand() {
@@ -392,7 +503,6 @@ class Ds extends core_1.Command {
392
503
  this.error('Docker Compose is not installed. Please install Docker Compose to manage infrastructure services.');
393
504
  }
394
505
  }
395
- return 'docker compose';
396
506
  }
397
507
  listComposeServices(composeCommand, cwd) {
398
508
  try {
@@ -484,44 +594,263 @@ class Ds extends core_1.Command {
484
594
  }
485
595
  }
486
596
  }
597
+ // ─── Indexes action ────────────────────────────────────────────────────────
598
+ async showIndexes(datasource, verbose = false) {
599
+ if (verbose) {
600
+ this.log(`Retrieving indexes for datasource '${datasource}'...`);
601
+ }
602
+ // Parse datasource config first so we can make engine-specific decisions
603
+ // (e.g. skip Docker validation entirely for SQLite)
604
+ const backendPath = (0, checkFramework_js_1.getBackendPath)();
605
+ const dsPath = node_path_1.default.join(backendPath, 'src', 'dataSources', `${datasource}.ts`);
606
+ const content = await fs_extra_1.default.readFile(dsPath, 'utf-8');
607
+ const dsOptions = (0, infraFileParser_js_1.parseDataSourceFile)(content, datasource);
608
+ if (!dsOptions) {
609
+ this.error(`Could not parse datasource configuration from '${datasource}.ts'.`);
610
+ }
611
+ const isSqlite = dsOptions.type === 'sqlite' || dsOptions.type === 'better-sqlite3';
612
+ // Docker validation and driver install are only needed for server-based databases.
613
+ // SQLite is a local file (or :memory:) — no container required.
614
+ if (!isSqlite) {
615
+ await this.validateDockerInfrastructure(datasource, verbose);
616
+ await this.ensureDatabaseDependencies(datasource, verbose);
617
+ }
618
+ const { DataSource: TypeOrmDataSource } = await import('typeorm');
619
+ const connectionConfig = {
620
+ type: dsOptions.type,
621
+ synchronize: false,
622
+ logging: false,
623
+ };
624
+ if (dsOptions.type === 'sqlite') {
625
+ connectionConfig.database = dsOptions.database || ':memory:';
626
+ }
627
+ else {
628
+ if (dsOptions.host)
629
+ connectionConfig.host = dsOptions.host;
630
+ if (dsOptions.port)
631
+ connectionConfig.port = dsOptions.port;
632
+ if (dsOptions.username !== undefined)
633
+ connectionConfig.username = dsOptions.username;
634
+ if (dsOptions.password !== undefined)
635
+ connectionConfig.password = dsOptions.password;
636
+ if (dsOptions.database)
637
+ connectionConfig.database = dsOptions.database;
638
+ }
639
+ let dataSource;
640
+ try {
641
+ if (verbose) {
642
+ this.log('Connecting to database (read-only, no schema sync)...');
643
+ }
644
+ dataSource = new TypeOrmDataSource(connectionConfig);
645
+ await dataSource.initialize();
646
+ }
647
+ catch (error) {
648
+ this.error(`Failed to connect to database: ${error.message}`);
649
+ }
650
+ try {
651
+ const indexes = await this.queryIndexes(dataSource, verbose);
652
+ if (indexes.length === 0) {
653
+ this.log('No indexes found in the database (excluding primary keys).');
654
+ return;
655
+ }
656
+ // Print the indexes grouped by table
657
+ this.printIndexTable(indexes);
658
+ }
659
+ finally {
660
+ if (dataSource.isInitialized) {
661
+ await dataSource.destroy();
662
+ if (verbose) {
663
+ this.log('Database connection closed');
664
+ }
665
+ }
666
+ }
667
+ }
668
+ async queryIndexes(dataSource, verbose) {
669
+ const dbType = dataSource.options.type;
670
+ if (verbose) {
671
+ this.log(`Querying indexes for database type: ${dbType}`);
672
+ }
673
+ switch (dbType) {
674
+ case 'postgres':
675
+ return this.queryPostgresIndexes(dataSource);
676
+ case 'mysql':
677
+ case 'mariadb':
678
+ return this.queryMysqlIndexes(dataSource);
679
+ case 'sqlite':
680
+ case 'better-sqlite3':
681
+ return this.querySqliteIndexes(dataSource);
682
+ default:
683
+ this.error(`Unsupported database type for index inspection: ${dbType}`);
684
+ }
685
+ }
686
+ async queryPostgresIndexes(dataSource) {
687
+ const queryRunner = dataSource.createQueryRunner();
688
+ try {
689
+ const rows = await queryRunner.query(`
690
+ SELECT
691
+ t.relname AS table_name,
692
+ i.relname AS index_name,
693
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) AS columns,
694
+ ix.indisunique AS is_unique,
695
+ am.amname AS algorithm
696
+ FROM pg_index ix
697
+ JOIN pg_class t ON t.oid = ix.indrelid
698
+ JOIN pg_class i ON i.oid = ix.indexrelid
699
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
700
+ JOIN pg_am am ON am.oid = i.relam
701
+ JOIN pg_namespace n ON n.oid = t.relnamespace
702
+ WHERE n.nspname = 'public'
703
+ AND NOT ix.indisprimary
704
+ GROUP BY t.relname, i.relname, ix.indisunique, am.amname
705
+ ORDER BY t.relname, i.relname
706
+ `);
707
+ return rows.map((row) => ({
708
+ tableName: row.table_name,
709
+ indexName: row.index_name,
710
+ columns: Array.isArray(row.columns) ? row.columns : [row.columns],
711
+ isUnique: row.is_unique,
712
+ algorithm: row.algorithm,
713
+ }));
714
+ }
715
+ finally {
716
+ await queryRunner.release();
717
+ }
718
+ }
719
+ async queryMysqlIndexes(dataSource) {
720
+ const queryRunner = dataSource.createQueryRunner();
721
+ try {
722
+ const rows = await queryRunner.query(`
723
+ SELECT
724
+ TABLE_NAME AS table_name,
725
+ INDEX_NAME AS index_name,
726
+ GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS columns_csv,
727
+ CASE WHEN NON_UNIQUE = 0 THEN 1 ELSE 0 END AS is_unique,
728
+ INDEX_TYPE AS algorithm
729
+ FROM INFORMATION_SCHEMA.STATISTICS
730
+ WHERE TABLE_SCHEMA = DATABASE()
731
+ AND INDEX_NAME != 'PRIMARY'
732
+ GROUP BY TABLE_NAME, INDEX_NAME, NON_UNIQUE, INDEX_TYPE
733
+ ORDER BY TABLE_NAME, INDEX_NAME
734
+ `);
735
+ return rows.map((row) => ({
736
+ tableName: row.table_name,
737
+ indexName: row.index_name,
738
+ columns: row.columns_csv.split(','),
739
+ isUnique: row.is_unique === 1 || row.is_unique === '1',
740
+ algorithm: row.algorithm.toLowerCase(),
741
+ }));
742
+ }
743
+ finally {
744
+ await queryRunner.release();
745
+ }
746
+ }
747
+ async querySqliteIndexes(dataSource) {
748
+ const queryRunner = dataSource.createQueryRunner();
749
+ const indexes = [];
750
+ try {
751
+ const tables = await queryRunner.query(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`);
752
+ for (const table of tables) {
753
+ const indexList = await queryRunner.query(`PRAGMA index_list("${table.name}")`);
754
+ for (const idx of indexList) {
755
+ // Skip auto-created indexes for primary keys
756
+ if (idx.origin === 'pk')
757
+ continue;
758
+ const indexInfo = await queryRunner.query(`PRAGMA index_info("${idx.name}")`);
759
+ const columns = indexInfo.map(col => col.name);
760
+ indexes.push({
761
+ tableName: table.name,
762
+ indexName: idx.name,
763
+ columns,
764
+ isUnique: idx.unique === 1,
765
+ algorithm: 'btree',
766
+ });
767
+ }
768
+ }
769
+ return indexes;
770
+ }
771
+ finally {
772
+ await queryRunner.release();
773
+ }
774
+ }
775
+ printIndexTable(indexes) {
776
+ // Calculate column widths
777
+ const headers = { table: 'TABLE', index: 'INDEX', columns: 'COLUMNS', unique: 'UNIQUE', algorithm: 'ALGORITHM' };
778
+ let tableW = headers.table.length;
779
+ let indexW = headers.index.length;
780
+ let columnsW = headers.columns.length;
781
+ const uniqueW = headers.unique.length;
782
+ let algoW = headers.algorithm.length;
783
+ for (const idx of indexes) {
784
+ tableW = Math.max(tableW, idx.tableName.length);
785
+ indexW = Math.max(indexW, idx.indexName.length);
786
+ columnsW = Math.max(columnsW, idx.columns.join(', ').length);
787
+ algoW = Math.max(algoW, idx.algorithm.length);
788
+ }
789
+ const pad = (str, width) => str.padEnd(width);
790
+ const separator = '-'.repeat(tableW + indexW + columnsW + uniqueW + algoW + 16);
791
+ this.log('');
792
+ this.log(` ${pad(headers.table, tableW)} ${pad(headers.index, indexW)} ${pad(headers.columns, columnsW)} ${pad(headers.unique, uniqueW)} ${headers.algorithm}`);
793
+ this.log(` ${separator}`);
794
+ let currentTable = '';
795
+ for (const idx of indexes) {
796
+ const tableDisplay = idx.tableName !== currentTable ? idx.tableName : '';
797
+ currentTable = idx.tableName;
798
+ this.log(` ${pad(tableDisplay, tableW)} ${pad(idx.indexName, indexW)} ${pad(idx.columns.join(', '), columnsW)} ${pad(idx.isUnique ? 'Yes' : 'No', uniqueW)} ${idx.algorithm}`);
799
+ }
800
+ this.log('');
801
+ this.log(` ${indexes.length} index${indexes.length !== 1 ? 'es' : ''} found.`);
802
+ }
487
803
  /**
488
- * Validate that Docker is running and PostgreSQL container is available
804
+ * Validate that Docker is running and the database container is available.
805
+ * Only call this for server-based databases (postgres, mysql, mariadb).
806
+ * SQLite does not need Docker and should bypass this method entirely.
489
807
  */
490
- async validateDockerInfrastructure(datasource) {
491
- // Check if Docker is running
808
+ async validateDockerInfrastructure(datasource, verbose = false) {
809
+ // Check if Docker is running (auto-start if needed)
810
+ const { ensureDockerIsRunning } = await import('../utils/dockerManager.js');
492
811
  try {
493
- (0, child_process_1.execSync)('docker info', { stdio: 'pipe' });
812
+ await ensureDockerIsRunning('slingr ds');
494
813
  }
495
814
  catch (error) {
496
- this.error(`Docker is not running. Please start Docker Desktop before running dataset commands.\n\nThe dataset command requires Docker to run the PostgreSQL infrastructure.`);
815
+ this.error(error.message);
497
816
  }
498
817
  // Check if docker-compose.yml exists
499
818
  const dockerComposePath = node_path_1.default.join(process.cwd(), 'docker-compose.yml');
500
819
  if (!(await fs_extra_1.default.pathExists(dockerComposePath))) {
501
820
  this.error(`Docker infrastructure not found. Missing docker-compose.yml file.\n\nTo fix this, run one of the following commands:\n - slingr infra:update --all (to generate infrastructure files)\n - slingr run (to automatically generate and start infrastructure)`);
502
821
  }
503
- // Check if PostgreSQL container is running
822
+ // Get the correct Docker Compose command
823
+ const composeCommand = this.getComposeCommand();
824
+ // Check if the database container is running
504
825
  const serviceName = `${datasource}-db`; // Docker service name format
826
+ if (verbose) {
827
+ this.log(`Verifying database container '${serviceName}' is running...`);
828
+ }
505
829
  try {
506
- const result = (0, child_process_1.execSync)(`docker-compose ps -q ${serviceName}`, {
830
+ const result = (0, child_process_1.execSync)(`${composeCommand} ps -q ${serviceName}`, {
507
831
  stdio: 'pipe',
508
832
  encoding: 'utf8',
509
833
  });
510
834
  if (!result.trim()) {
511
- this.error(`PostgreSQL container '${serviceName}' is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - docker-compose up -d ${serviceName} (to start just the PostgreSQL service)`);
835
+ this.error(`Database container '${serviceName}' is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - ${composeCommand} up -d ${serviceName} (to start just the ${datasource} service)`);
836
+ }
837
+ if (verbose) {
838
+ this.log(`✅ Database container '${serviceName}' exists`);
512
839
  }
513
840
  // Verify the container is actually running (not just exists)
514
- const statusResult = (0, child_process_1.execSync)(`docker-compose ps ${serviceName}`, {
841
+ const statusResult = (0, child_process_1.execSync)(`${composeCommand} ps ${serviceName}`, {
515
842
  stdio: 'pipe',
516
843
  encoding: 'utf8',
517
844
  });
518
845
  if (!statusResult.includes('Up')) {
519
- this.error(`PostgreSQL container '${serviceName}' exists but is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - docker-compose up -d ${serviceName} (to start just the PostgreSQL service)`);
846
+ this.error(`Database container '${serviceName}' exists but is not running.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - ${composeCommand} up -d ${serviceName} (to start just the ${datasource} service)`);
847
+ }
848
+ if (verbose) {
849
+ this.log(`✅ Docker infrastructure validated: database container '${serviceName}' is running`);
520
850
  }
521
- this.log(`✅ Docker infrastructure validated: PostgreSQL container '${serviceName}' is running`);
522
851
  }
523
852
  catch (error) {
524
- this.error(`Could not verify PostgreSQL container status.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - docker-compose up -d (to start all infrastructure services)`);
853
+ this.error(`Could not verify database container status.\n\nTo fix this, run one of the following commands:\n - slingr run (to start the full application with infrastructure)\n - ${composeCommand} up -d (to start all infrastructure services)`);
525
854
  }
526
855
  }
527
856
  }