@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.
- package/LICENSE.txt +202 -0
- package/README.md +490 -319
- package/bin/dev.cmd +2 -2
- package/bin/dev.js +5 -5
- package/bin/run.cmd +2 -2
- package/bin/run.js +4 -4
- package/bin/slingr +1 -0
- package/dist/commands/build.d.ts +20 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +206 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/create-app.d.ts +0 -1
- package/dist/commands/create-app.d.ts.map +1 -1
- package/dist/commands/create-app.js +38 -57
- package/dist/commands/create-app.js.map +1 -1
- package/dist/commands/debug.d.ts +28 -0
- package/dist/commands/debug.d.ts.map +1 -0
- package/dist/commands/debug.js +474 -0
- package/dist/commands/debug.js.map +1 -0
- package/dist/commands/ds.d.ts +14 -1
- package/dist/commands/ds.d.ts.map +1 -1
- package/dist/commands/ds.js +450 -121
- package/dist/commands/ds.js.map +1 -1
- package/dist/commands/gql.d.ts +1 -1
- package/dist/commands/gql.d.ts.map +1 -1
- package/dist/commands/gql.js +190 -184
- package/dist/commands/gql.js.map +1 -1
- package/dist/commands/infra/down.d.ts.map +1 -1
- package/dist/commands/infra/down.js +8 -7
- package/dist/commands/infra/down.js.map +1 -1
- package/dist/commands/infra/up.d.ts.map +1 -1
- package/dist/commands/infra/up.js +8 -7
- package/dist/commands/infra/up.js.map +1 -1
- package/dist/commands/infra/update.d.ts +1 -0
- package/dist/commands/infra/update.d.ts.map +1 -1
- package/dist/commands/infra/update.js +33 -69
- package/dist/commands/infra/update.js.map +1 -1
- package/dist/commands/run.d.ts +29 -2
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +628 -130
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +34 -71
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/sync-metadata.d.ts +15 -0
- package/dist/commands/sync-metadata.d.ts.map +1 -0
- package/dist/commands/sync-metadata.js +225 -0
- package/dist/commands/sync-metadata.js.map +1 -0
- package/dist/commands/users.d.ts +30 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/users.js +472 -0
- package/dist/commands/users.js.map +1 -0
- package/dist/commands/views.d.ts +11 -0
- package/dist/commands/views.d.ts.map +1 -0
- package/dist/commands/views.js +73 -0
- package/dist/commands/views.js.map +1 -0
- package/dist/projectStructure.d.ts +2 -2
- package/dist/projectStructure.d.ts.map +1 -1
- package/dist/projectStructure.js +281 -69
- package/dist/projectStructure.js.map +1 -1
- package/dist/scripts/generate-metadata.d.ts +13 -0
- package/dist/scripts/generate-metadata.d.ts.map +1 -0
- package/dist/scripts/generate-metadata.js +412 -0
- package/dist/scripts/generate-metadata.js.map +1 -0
- package/dist/scripts/generate-metadata.ts +498 -0
- package/dist/scripts/generate-schema.d.ts +1 -1
- package/dist/scripts/generate-schema.js +168 -74
- package/dist/scripts/generate-schema.js.map +1 -1
- package/dist/scripts/generate-schema.ts +258 -143
- package/dist/templates/.env.template +23 -0
- package/dist/templates/.firebaserc.template +5 -0
- package/dist/templates/.github/copilot-instructions.md.template +652 -17
- package/dist/templates/backend/Dockerfile.template +30 -0
- package/dist/templates/config/datasource.ts.template +12 -9
- package/dist/templates/config/jest.config.ts +30 -30
- package/dist/templates/config/jest.setup.ts +1 -1
- package/dist/templates/config/tsconfig.json.template +50 -29
- package/dist/templates/dataSources/mysql.ts.template +16 -13
- package/dist/templates/dataSources/postgres.ts.template +15 -13
- package/dist/templates/dataset-generator-script.ts.template +139 -139
- package/dist/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
- package/dist/templates/datasets/mysql-default/Address.jsonl.template +3 -3
- package/dist/templates/datasets/mysql-default/App.jsonl.template +4 -4
- package/dist/templates/datasets/mysql-default/Company.jsonl.template +3 -3
- package/dist/templates/datasets/mysql-default/Person.jsonl.template +2 -2
- package/dist/templates/datasets/mysql-default/User.jsonl.template +1 -0
- package/dist/templates/datasets/mysql-default/instructions.md.template +1 -0
- package/dist/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
- package/dist/templates/datasets/postgres-default/Address.jsonl.template +3 -3
- package/dist/templates/datasets/postgres-default/App.jsonl.template +4 -4
- package/dist/templates/datasets/postgres-default/Company.jsonl.template +3 -3
- package/dist/templates/datasets/postgres-default/Person.jsonl.template +2 -2
- package/dist/templates/datasets/postgres-default/User.jsonl.template +1 -0
- package/dist/templates/datasets/postgres-default/instructions.md.template +1 -0
- package/dist/templates/docker-compose.prod-test.yml.template +32 -0
- package/dist/templates/docker-compose.yml.template +24 -0
- package/dist/templates/docs/app-description.md.template +33 -33
- package/dist/templates/firebase.json.template +68 -0
- package/dist/templates/frontend/.umirc.ts.template +23 -0
- package/dist/templates/frontend/package.json.template +45 -0
- package/dist/templates/frontend/public/config.json +6 -0
- package/dist/templates/frontend/public/logo.svg +6 -0
- package/dist/templates/frontend/src/app.tsx.template +44 -0
- package/dist/templates/frontend/src/global.less.template +117 -0
- package/dist/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
- package/dist/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
- package/dist/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
- package/dist/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
- package/dist/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
- package/dist/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
- package/dist/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
- package/dist/templates/frontend/tsconfig.json.template +50 -0
- package/dist/templates/gql/codegen.yml.template +25 -25
- package/dist/templates/gql/index.ts.template +17 -24
- package/dist/templates/gql/operations.graphql.template +30 -30
- package/dist/templates/ops/README.md.template +1045 -0
- package/dist/templates/ops/cloudbuild.yaml.template +161 -0
- package/dist/templates/ops/scripts/_utils.js.template +217 -0
- package/dist/templates/ops/scripts/deploy.js.template +145 -0
- package/dist/templates/ops/scripts/setup-gcp.js.template +330 -0
- package/dist/templates/ops/scripts/setup-secrets.js.template +76 -0
- package/dist/templates/ops/scripts/test-prod-local.js.template +49 -0
- package/dist/templates/package.json.template +50 -38
- package/dist/templates/pnpm-workspace.yaml.template +3 -0
- package/dist/templates/prompt-analysis.md.template +110 -110
- package/dist/templates/prompt-script-generation.md.template +258 -258
- package/dist/templates/src/Address.ts.template +28 -31
- package/dist/templates/src/App.ts.template +17 -61
- package/dist/templates/src/Company.ts.template +41 -47
- package/dist/templates/src/Models.test.ts.template +654 -654
- package/dist/templates/src/Person.test.ts.template +289 -289
- package/dist/templates/src/Person.ts.template +90 -105
- package/dist/templates/src/actions/index.ts.template +11 -11
- package/dist/templates/src/auth/permissions.ts.template +34 -0
- package/dist/templates/src/data/App.ts.template +48 -0
- package/dist/templates/src/data/User.ts.template +35 -0
- package/dist/templates/src/types/gql.d.ts.template +17 -17
- package/dist/templates/vscode/extensions.json +4 -3
- package/dist/templates/vscode/settings.json +17 -11
- package/dist/templates/workspace-package.json.template +21 -0
- package/dist/utils/buildCache.d.ts +12 -0
- package/dist/utils/buildCache.d.ts.map +1 -0
- package/dist/utils/buildCache.js +102 -0
- package/dist/utils/buildCache.js.map +1 -0
- package/dist/utils/checkFramework.d.ts +27 -0
- package/dist/utils/checkFramework.d.ts.map +1 -0
- package/dist/utils/checkFramework.js +104 -0
- package/dist/utils/checkFramework.js.map +1 -0
- package/dist/utils/datasourceParser.d.ts +11 -0
- package/dist/utils/datasourceParser.d.ts.map +1 -1
- package/dist/utils/datasourceParser.js +154 -56
- package/dist/utils/datasourceParser.js.map +1 -1
- package/dist/utils/dockerManager.d.ts +25 -0
- package/dist/utils/dockerManager.d.ts.map +1 -0
- package/dist/utils/dockerManager.js +281 -0
- package/dist/utils/dockerManager.js.map +1 -0
- package/dist/utils/infraFileParser.d.ts +26 -0
- package/dist/utils/infraFileParser.d.ts.map +1 -0
- package/dist/utils/infraFileParser.js +75 -0
- package/dist/utils/infraFileParser.js.map +1 -0
- package/dist/utils/jsonlLoader.d.ts +91 -12
- package/dist/utils/jsonlLoader.d.ts.map +1 -1
- package/dist/utils/jsonlLoader.js +674 -63
- package/dist/utils/jsonlLoader.js.map +1 -1
- package/dist/utils/model-analyzer.d.ts.map +1 -1
- package/dist/utils/model-analyzer.js +67 -13
- package/dist/utils/model-analyzer.js.map +1 -1
- package/dist/utils/userManagement.d.ts +57 -0
- package/dist/utils/userManagement.d.ts.map +1 -0
- package/dist/utils/userManagement.js +288 -0
- package/dist/utils/userManagement.js.map +1 -0
- package/dist/utils/viewsGenerator.d.ts +15 -0
- package/dist/utils/viewsGenerator.d.ts.map +1 -0
- package/dist/utils/viewsGenerator.js +311 -0
- package/dist/utils/viewsGenerator.js.map +1 -0
- package/oclif.manifest.json +445 -20
- package/package.json +29 -26
- package/src/templates/.env.template +23 -0
- package/src/templates/.firebaserc.template +5 -0
- package/src/templates/.github/copilot-instructions.md.template +652 -17
- package/src/templates/backend/Dockerfile.template +30 -0
- package/src/templates/config/datasource.ts.template +12 -9
- package/src/templates/config/jest.config.ts +30 -30
- package/src/templates/config/jest.setup.ts +1 -1
- package/src/templates/config/tsconfig.json.template +50 -29
- package/src/templates/dataSources/mysql.ts.template +16 -13
- package/src/templates/dataSources/postgres.ts.template +15 -13
- package/src/templates/dataset-generator-script.ts.template +139 -139
- package/src/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
- package/src/templates/datasets/mysql-default/Address.jsonl.template +3 -3
- package/src/templates/datasets/mysql-default/App.jsonl.template +4 -4
- package/src/templates/datasets/mysql-default/Company.jsonl.template +3 -3
- package/src/templates/datasets/mysql-default/Person.jsonl.template +2 -2
- package/src/templates/datasets/mysql-default/User.jsonl.template +1 -0
- package/src/templates/datasets/mysql-default/instructions.md.template +1 -0
- package/src/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
- package/src/templates/datasets/postgres-default/Address.jsonl.template +3 -3
- package/src/templates/datasets/postgres-default/App.jsonl.template +4 -4
- package/src/templates/datasets/postgres-default/Company.jsonl.template +3 -3
- package/src/templates/datasets/postgres-default/Person.jsonl.template +2 -2
- package/src/templates/datasets/postgres-default/User.jsonl.template +1 -0
- package/src/templates/datasets/postgres-default/instructions.md.template +1 -0
- package/src/templates/docker-compose.prod-test.yml.template +32 -0
- package/src/templates/docker-compose.yml.template +24 -0
- package/src/templates/docs/app-description.md.template +33 -33
- package/src/templates/firebase.json.template +68 -0
- package/src/templates/frontend/.umirc.ts.template +23 -0
- package/src/templates/frontend/package.json.template +45 -0
- package/src/templates/frontend/public/config.json +6 -0
- package/src/templates/frontend/public/logo.svg +6 -0
- package/src/templates/frontend/src/app.tsx.template +44 -0
- package/src/templates/frontend/src/global.less.template +117 -0
- package/src/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
- package/src/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
- package/src/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
- package/src/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
- package/src/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
- package/src/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
- package/src/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
- package/src/templates/frontend/tsconfig.json.template +50 -0
- package/src/templates/gql/codegen.yml.template +25 -25
- package/src/templates/gql/index.ts.template +17 -24
- package/src/templates/gql/operations.graphql.template +30 -30
- package/src/templates/ops/README.md.template +1045 -0
- package/src/templates/ops/cloudbuild.yaml.template +161 -0
- package/src/templates/ops/scripts/_utils.js.template +217 -0
- package/src/templates/ops/scripts/deploy.js.template +145 -0
- package/src/templates/ops/scripts/setup-gcp.js.template +330 -0
- package/src/templates/ops/scripts/setup-secrets.js.template +76 -0
- package/src/templates/ops/scripts/test-prod-local.js.template +49 -0
- package/src/templates/package.json.template +50 -38
- package/src/templates/pnpm-workspace.yaml.template +3 -0
- package/src/templates/prompt-analysis.md.template +110 -110
- package/src/templates/prompt-script-generation.md.template +258 -258
- package/src/templates/src/Address.ts.template +28 -31
- package/src/templates/src/App.ts.template +17 -61
- package/src/templates/src/Company.ts.template +41 -47
- package/src/templates/src/Models.test.ts.template +654 -654
- package/src/templates/src/Person.test.ts.template +289 -289
- package/src/templates/src/Person.ts.template +90 -105
- package/src/templates/src/actions/index.ts.template +11 -11
- package/src/templates/src/auth/permissions.ts.template +34 -0
- package/src/templates/src/data/App.ts.template +48 -0
- package/src/templates/src/data/User.ts.template +35 -0
- package/src/templates/src/types/gql.d.ts.template +17 -17
- package/src/templates/vscode/extensions.json +4 -3
- package/src/templates/vscode/settings.json +17 -11
- package/src/templates/workspace-package.json.template +21 -0
- package/dist/templates/src/index.ts +0 -66
- package/src/templates/src/index.ts +0 -66
package/dist/commands/ds.js
CHANGED
|
@@ -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
|
|
49
|
+
static description = 'Manage datasets and inspect datasources';
|
|
47
50
|
static examples = [
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
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
|
|
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
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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}
|
|
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
|
-
|
|
103
|
-
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
185
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
186
|
+
cwd: backendPath,
|
|
147
187
|
});
|
|
148
|
-
|
|
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
|
-
|
|
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-
|
|
160
|
-
|
|
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
|
|
166
|
-
const
|
|
167
|
-
if (!
|
|
168
|
-
this.error('
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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 (
|
|
214
|
-
|
|
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
|
-
//
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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,
|
|
383
|
+
await loader.loadDatasetToDatabase(loadResults, dsConfig, verbose);
|
|
288
384
|
// Print summary
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
this.log(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
486
|
+
await ensureDockerIsRunning('slingr ds');
|
|
376
487
|
}
|
|
377
488
|
catch (error) {
|
|
378
|
-
this.error(
|
|
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
|
|
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
|
-
|
|
812
|
+
await ensureDockerIsRunning('slingr ds');
|
|
494
813
|
}
|
|
495
814
|
catch (error) {
|
|
496
|
-
this.error(
|
|
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
|
-
//
|
|
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)(
|
|
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(`
|
|
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)(
|
|
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(`
|
|
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
|
|
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
|
}
|