relq 1.0.8 → 1.0.10
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/dist/bin/relq.js +2 -1
- package/dist/cjs/addon/pg/index.cjs +3 -2
- package/dist/cjs/addon/pg-cursor/index.cjs +3 -2
- package/dist/cjs/cli/commands/add.cjs +4 -1
- package/dist/cjs/cli/commands/commit.cjs +5 -1
- package/dist/cjs/cli/commands/import.cjs +4 -2
- package/dist/cjs/cli/commands/pull.cjs +1 -1
- package/dist/cjs/cli/commands/validate.cjs +69 -0
- package/dist/cjs/cli/index.cjs +6 -1
- package/dist/cjs/cli/utils/config-loader.cjs +41 -0
- package/dist/cjs/cli/utils/schema-validator.cjs +147 -0
- package/dist/esm/addon/pg/index.js +3 -2
- package/dist/esm/addon/pg-cursor/index.js +3 -2
- package/dist/esm/cli/commands/add.js +4 -1
- package/dist/esm/cli/commands/commit.js +5 -1
- package/dist/esm/cli/commands/import.js +4 -2
- package/dist/esm/cli/commands/pull.js +2 -2
- package/dist/esm/cli/commands/validate.js +33 -0
- package/dist/esm/cli/index.js +6 -1
- package/dist/esm/cli/utils/config-loader.js +39 -0
- package/dist/esm/cli/utils/schema-validator.js +110 -0
- package/package.json +1 -1
package/dist/bin/relq.js
CHANGED
|
@@ -346,9 +346,10 @@ var require_postgres_interval = __commonJS((exports2, module2) => {
|
|
|
346
346
|
|
|
347
347
|
// node_modules/postgres-bytea/index.js
|
|
348
348
|
var require_postgres_bytea = __commonJS((exports2, module2) => {
|
|
349
|
+
var bufferFrom = Buffer.from || Buffer;
|
|
349
350
|
module2.exports = function parseBytea(input) {
|
|
350
351
|
if (/^\\x/.test(input)) {
|
|
351
|
-
return
|
|
352
|
+
return bufferFrom(input.substr(2), "hex");
|
|
352
353
|
}
|
|
353
354
|
var output = "";
|
|
354
355
|
var i = 0;
|
|
@@ -372,7 +373,7 @@ var require_postgres_bytea = __commonJS((exports2, module2) => {
|
|
|
372
373
|
}
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
|
-
return
|
|
376
|
+
return bufferFrom(output, "binary");
|
|
376
377
|
};
|
|
377
378
|
});
|
|
378
379
|
|
|
@@ -346,9 +346,10 @@ var require_postgres_interval = __commonJS((exports2, module2) => {
|
|
|
346
346
|
|
|
347
347
|
// node_modules/postgres-bytea/index.js
|
|
348
348
|
var require_postgres_bytea = __commonJS((exports2, module2) => {
|
|
349
|
+
var bufferFrom = Buffer.from || Buffer;
|
|
349
350
|
module2.exports = function parseBytea(input) {
|
|
350
351
|
if (/^\\x/.test(input)) {
|
|
351
|
-
return
|
|
352
|
+
return bufferFrom(input.substr(2), "hex");
|
|
352
353
|
}
|
|
353
354
|
var output = "";
|
|
354
355
|
var i = 0;
|
|
@@ -372,7 +373,7 @@ var require_postgres_bytea = __commonJS((exports2, module2) => {
|
|
|
372
373
|
}
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
|
-
return
|
|
376
|
+
return bufferFrom(output, "binary");
|
|
376
377
|
};
|
|
377
378
|
});
|
|
378
379
|
|
|
@@ -47,6 +47,7 @@ const path = __importStar(require("path"));
|
|
|
47
47
|
const repo_manager_1 = require("../utils/repo-manager.cjs");
|
|
48
48
|
const change_tracker_1 = require("../utils/change-tracker.cjs");
|
|
49
49
|
const schema_comparator_1 = require("../utils/schema-comparator.cjs");
|
|
50
|
+
const config_loader_1 = require("../utils/config-loader.cjs");
|
|
50
51
|
function parseSchemaFileForComparison(schemaPath) {
|
|
51
52
|
if (!fs.existsSync(schemaPath)) {
|
|
52
53
|
return null;
|
|
@@ -623,8 +624,10 @@ async function addCommand(context) {
|
|
|
623
624
|
}
|
|
624
625
|
const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
|
|
625
626
|
const config = await (0, config_1.loadConfig)();
|
|
626
|
-
const schemaPathRaw =
|
|
627
|
+
const schemaPathRaw = (0, config_loader_1.getSchemaPath)(config);
|
|
627
628
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
629
|
+
const { requireValidSchema } = await Promise.resolve().then(() => __importStar(require("../utils/config-loader.cjs")));
|
|
630
|
+
await requireValidSchema(schemaPath, context.flags);
|
|
628
631
|
const injectedCount = injectTrackingIds(schemaPath);
|
|
629
632
|
if (injectedCount > 0) {
|
|
630
633
|
console.log(spinner_1.colors.muted(`Injected ${injectedCount} tracking ID(s) into schema.ts`));
|
|
@@ -38,6 +38,7 @@ const crypto = __importStar(require("crypto"));
|
|
|
38
38
|
const cli_utils_1 = require("../utils/cli-utils.cjs");
|
|
39
39
|
const repo_manager_1 = require("../utils/repo-manager.cjs");
|
|
40
40
|
const config_1 = require("../../config/config.cjs");
|
|
41
|
+
const config_loader_1 = require("../utils/config-loader.cjs");
|
|
41
42
|
const change_tracker_1 = require("../utils/change-tracker.cjs");
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
@@ -48,6 +49,9 @@ async function commitCommand(context) {
|
|
|
48
49
|
if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
|
|
49
50
|
(0, cli_utils_1.fatal)('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
|
|
50
51
|
}
|
|
52
|
+
const schemaPath = path.resolve(projectRoot, (0, config_loader_1.getSchemaPath)(config ?? undefined));
|
|
53
|
+
const { requireValidSchema } = await Promise.resolve().then(() => __importStar(require("../utils/config-loader.cjs")));
|
|
54
|
+
await requireValidSchema(schemaPath, flags);
|
|
51
55
|
const staged = (0, repo_manager_1.getStagedChanges)(projectRoot);
|
|
52
56
|
if (staged.length === 0) {
|
|
53
57
|
console.log('nothing to commit, working tree clean');
|
|
@@ -122,7 +126,7 @@ async function commitCommand(context) {
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
const commitConfig = await (0, config_1.loadConfig)();
|
|
125
|
-
const schemaPathRaw =
|
|
129
|
+
const schemaPathRaw = (0, config_loader_1.getSchemaPath)(commitConfig);
|
|
126
130
|
const schemaFilePath = path.resolve(projectRoot, schemaPathRaw);
|
|
127
131
|
if (fs.existsSync(schemaFilePath)) {
|
|
128
132
|
const currentContent = fs.readFileSync(schemaFilePath, 'utf-8');
|
|
@@ -42,11 +42,13 @@ const ast_transformer_1 = require("../utils/ast-transformer.cjs");
|
|
|
42
42
|
const repo_manager_1 = require("../utils/repo-manager.cjs");
|
|
43
43
|
const schema_comparator_1 = require("../utils/schema-comparator.cjs");
|
|
44
44
|
const change_tracker_1 = require("../utils/change-tracker.cjs");
|
|
45
|
+
const config_loader_1 = require("../utils/config-loader.cjs");
|
|
45
46
|
const relqignore_1 = require("../utils/relqignore.cjs");
|
|
46
47
|
const git_utils_1 = require("../utils/git-utils.cjs");
|
|
47
48
|
async function importCommand(sqlFilePath, options = {}, projectRoot = process.cwd()) {
|
|
48
49
|
const { includeFunctions = false, includeTriggers = false, force = false, dryRun = false } = options;
|
|
49
50
|
const spinner = (0, git_utils_1.createSpinner)();
|
|
51
|
+
const config = await (0, config_loader_1.loadConfigWithEnv)();
|
|
50
52
|
console.log('');
|
|
51
53
|
if (!sqlFilePath) {
|
|
52
54
|
(0, git_utils_1.fatal)('No SQL file specified', 'usage: relq import <sql-file> [options]\n\n' +
|
|
@@ -396,7 +398,7 @@ async function importCommand(sqlFilePath, options = {}, projectRoot = process.cw
|
|
|
396
398
|
console.log(`${git_utils_1.colors.yellow('Dry run mode')} - no files written`);
|
|
397
399
|
console.log('');
|
|
398
400
|
console.log('Would write:');
|
|
399
|
-
const dryRunOutputPath = options.output ||
|
|
401
|
+
const dryRunOutputPath = options.output || (0, config_loader_1.getSchemaPath)(config);
|
|
400
402
|
const dryRunAbsPath = path.resolve(projectRoot, dryRunOutputPath);
|
|
401
403
|
console.log(` ${git_utils_1.colors.cyan(dryRunAbsPath)} ${git_utils_1.colors.gray(`(${(0, git_utils_1.formatBytes)(finalTypescriptContent.length)})`)}`);
|
|
402
404
|
console.log(` ${git_utils_1.colors.cyan(path.join(projectRoot, '.relq/snapshot.json'))}`);
|
|
@@ -406,7 +408,7 @@ async function importCommand(sqlFilePath, options = {}, projectRoot = process.cw
|
|
|
406
408
|
console.log('');
|
|
407
409
|
return;
|
|
408
410
|
}
|
|
409
|
-
const outputPath = options.output ||
|
|
411
|
+
const outputPath = options.output || (0, config_loader_1.getSchemaPath)(config);
|
|
410
412
|
const absoluteOutputPath = path.resolve(projectRoot, outputPath);
|
|
411
413
|
const outputDir = path.dirname(absoluteOutputPath);
|
|
412
414
|
if (!fs.existsSync(outputDir)) {
|
|
@@ -180,7 +180,7 @@ async function pullCommand(context) {
|
|
|
180
180
|
const noCommit = flags['no-commit'] === true;
|
|
181
181
|
const dryRun = flags['dry-run'] === true;
|
|
182
182
|
const author = config.author || 'Relq CLI';
|
|
183
|
-
const schemaPathRaw =
|
|
183
|
+
const schemaPathRaw = (0, config_loader_1.getSchemaPath)(config);
|
|
184
184
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
185
185
|
const includeFunctions = config.includeFunctions ?? true;
|
|
186
186
|
const includeTriggers = config.includeTriggers ?? true;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateCommand = validateCommand;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const config_loader_1 = require("../utils/config-loader.cjs");
|
|
39
|
+
const schema_validator_1 = require("../utils/schema-validator.cjs");
|
|
40
|
+
const cli_utils_1 = require("../utils/cli-utils.cjs");
|
|
41
|
+
async function validateCommand(context) {
|
|
42
|
+
const { config, args, projectRoot } = context;
|
|
43
|
+
console.log('');
|
|
44
|
+
let schemaPath;
|
|
45
|
+
if (args.length > 0) {
|
|
46
|
+
schemaPath = path.resolve(projectRoot, args[0]);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
schemaPath = path.resolve(projectRoot, (0, config_loader_1.getSchemaPath)(config ?? undefined));
|
|
50
|
+
}
|
|
51
|
+
const relativePath = path.relative(process.cwd(), schemaPath);
|
|
52
|
+
console.log(`Validating ${cli_utils_1.colors.cyan(relativePath)}...`);
|
|
53
|
+
console.log('');
|
|
54
|
+
const result = (0, schema_validator_1.validateSchemaFile)(schemaPath);
|
|
55
|
+
if (result.valid) {
|
|
56
|
+
(0, cli_utils_1.success)('Schema is valid');
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(` No syntax errors found in ${relativePath}`);
|
|
59
|
+
console.log('');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.log(cli_utils_1.colors.red(`error: Schema has ${result.errors.length} syntax error(s)`));
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log((0, schema_validator_1.formatValidationErrors)(result));
|
|
65
|
+
console.log(cli_utils_1.colors.yellow('hint:') + ' Fix the errors above before running other commands');
|
|
66
|
+
console.log('');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
exports.default = validateCommand;
|
package/dist/cjs/cli/index.cjs
CHANGED
|
@@ -57,6 +57,7 @@ const merge_1 = require("./commands/merge.cjs");
|
|
|
57
57
|
const tag_1 = require("./commands/tag.cjs");
|
|
58
58
|
const cherry_pick_1 = require("./commands/cherry-pick.cjs");
|
|
59
59
|
const remote_1 = require("./commands/remote.cjs");
|
|
60
|
+
const validate_1 = require("./commands/validate.cjs");
|
|
60
61
|
const fs = __importStar(require("fs"));
|
|
61
62
|
const path = __importStar(require("path"));
|
|
62
63
|
function loadEnvFile() {
|
|
@@ -161,6 +162,7 @@ Sync Commands:
|
|
|
161
162
|
sync Pull + push in one command
|
|
162
163
|
|
|
163
164
|
Other Commands:
|
|
165
|
+
validate Check schema for errors
|
|
164
166
|
diff [--sql] Show schema differences
|
|
165
167
|
generate Generate TypeScript types
|
|
166
168
|
introspect Parse database schema
|
|
@@ -193,7 +195,7 @@ function printVersion() {
|
|
|
193
195
|
console.log(`relq v${VERSION}`);
|
|
194
196
|
}
|
|
195
197
|
function requiresConfig(command) {
|
|
196
|
-
return !['init', 'introspect', 'import', 'help', 'version'].includes(command);
|
|
198
|
+
return !['init', 'introspect', 'import', 'help', 'version', 'validate'].includes(command);
|
|
197
199
|
}
|
|
198
200
|
function requiresDbConnection(command, flags) {
|
|
199
201
|
const alwaysNeedDb = ['pull', 'push', 'fetch', 'introspect'];
|
|
@@ -355,6 +357,9 @@ async function main() {
|
|
|
355
357
|
case 'introspect':
|
|
356
358
|
await (0, introspect_1.introspectCommand)(context);
|
|
357
359
|
break;
|
|
360
|
+
case 'validate':
|
|
361
|
+
await (0, validate_1.validateCommand)(context);
|
|
362
|
+
break;
|
|
358
363
|
case 'sync':
|
|
359
364
|
await (0, sync_1.syncCommand)(context);
|
|
360
365
|
break;
|
|
@@ -38,7 +38,9 @@ exports.findConfigFileRecursive = findConfigFileRecursive;
|
|
|
38
38
|
exports.configExists = configExists;
|
|
39
39
|
exports.loadConfigWithEnv = loadConfigWithEnv;
|
|
40
40
|
exports.validateConfig = validateConfig;
|
|
41
|
+
exports.getSchemaPath = getSchemaPath;
|
|
41
42
|
exports.requireValidConfig = requireValidConfig;
|
|
43
|
+
exports.requireValidSchema = requireValidSchema;
|
|
42
44
|
const config_1 = require("../../config/config.cjs");
|
|
43
45
|
const env_loader_1 = require("./env-loader.cjs");
|
|
44
46
|
const fs = __importStar(require("fs"));
|
|
@@ -114,6 +116,23 @@ function validateConfig(config) {
|
|
|
114
116
|
}
|
|
115
117
|
return errors;
|
|
116
118
|
}
|
|
119
|
+
function getSchemaPath(config, defaultPath = './db/schema.ts') {
|
|
120
|
+
if (!config)
|
|
121
|
+
return defaultPath;
|
|
122
|
+
if (typeof config.schema === 'string' && config.schema.length > 0) {
|
|
123
|
+
return config.schema;
|
|
124
|
+
}
|
|
125
|
+
if (typeof config.schema === 'object' && config.schema?.directory) {
|
|
126
|
+
return `${config.schema.directory}/schema.ts`;
|
|
127
|
+
}
|
|
128
|
+
if (config.generate?.outDir) {
|
|
129
|
+
return `${config.generate.outDir}/schema.ts`;
|
|
130
|
+
}
|
|
131
|
+
if (config.typeGeneration?.output) {
|
|
132
|
+
return config.typeGeneration.output;
|
|
133
|
+
}
|
|
134
|
+
return defaultPath;
|
|
135
|
+
}
|
|
117
136
|
async function requireValidConfig(config, options) {
|
|
118
137
|
const errors = validateConfig(config);
|
|
119
138
|
if (errors.length === 0)
|
|
@@ -151,3 +170,25 @@ async function requireValidConfig(config, options) {
|
|
|
151
170
|
console.error('');
|
|
152
171
|
process.exit(1);
|
|
153
172
|
}
|
|
173
|
+
async function requireValidSchema(schemaPath, flags = {}) {
|
|
174
|
+
if (flags['skip-validation'] === true) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
const { validateSchemaFile, formatValidationErrors } = await Promise.resolve().then(() => __importStar(require("./schema-validator.cjs")));
|
|
178
|
+
const result = validateSchemaFile(schemaPath);
|
|
179
|
+
if (result.valid) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const colors = {
|
|
183
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
184
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
185
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
186
|
+
};
|
|
187
|
+
console.error('');
|
|
188
|
+
console.error(colors.red('error:') + ` Schema has ${result.errors.length} syntax error(s)`);
|
|
189
|
+
console.error('');
|
|
190
|
+
console.error(formatValidationErrors(result));
|
|
191
|
+
console.error(colors.yellow('hint:') + ` Fix the errors above, or use ${colors.cyan('--skip-validation')} to bypass`);
|
|
192
|
+
console.error('');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateSchemaFile = validateSchemaFile;
|
|
37
|
+
exports.formatValidationErrors = formatValidationErrors;
|
|
38
|
+
const ts = __importStar(require("typescript"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
function validateSchemaFile(schemaPath) {
|
|
42
|
+
const absolutePath = path.resolve(schemaPath);
|
|
43
|
+
if (!fs.existsSync(absolutePath)) {
|
|
44
|
+
return {
|
|
45
|
+
valid: false,
|
|
46
|
+
errors: [{
|
|
47
|
+
line: 0,
|
|
48
|
+
column: 0,
|
|
49
|
+
message: `Schema file not found: ${absolutePath}`,
|
|
50
|
+
code: -1,
|
|
51
|
+
}],
|
|
52
|
+
filePath: absolutePath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const content = fs.readFileSync(absolutePath, 'utf-8');
|
|
56
|
+
const sourceFile = ts.createSourceFile(absolutePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
57
|
+
const errors = [];
|
|
58
|
+
const compilerOptions = {
|
|
59
|
+
target: ts.ScriptTarget.ESNext,
|
|
60
|
+
module: ts.ModuleKind.ESNext,
|
|
61
|
+
strict: false,
|
|
62
|
+
skipLibCheck: true,
|
|
63
|
+
noEmit: true,
|
|
64
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
65
|
+
};
|
|
66
|
+
const program = ts.createProgram([absolutePath], compilerOptions, {
|
|
67
|
+
getSourceFile: (fileName) => {
|
|
68
|
+
if (fileName === absolutePath)
|
|
69
|
+
return sourceFile;
|
|
70
|
+
return ts.createSourceFile(fileName, '', ts.ScriptTarget.Latest, true);
|
|
71
|
+
},
|
|
72
|
+
getDefaultLibFileName: () => 'lib.d.ts',
|
|
73
|
+
writeFile: () => { },
|
|
74
|
+
getCurrentDirectory: () => path.dirname(absolutePath),
|
|
75
|
+
getDirectories: () => [],
|
|
76
|
+
fileExists: (fileName) => fileName === absolutePath || fs.existsSync(fileName),
|
|
77
|
+
readFile: (fileName) => {
|
|
78
|
+
if (fileName === absolutePath)
|
|
79
|
+
return content;
|
|
80
|
+
try {
|
|
81
|
+
return fs.readFileSync(fileName, 'utf-8');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
88
|
+
useCaseSensitiveFileNames: () => true,
|
|
89
|
+
getNewLine: () => '\n',
|
|
90
|
+
});
|
|
91
|
+
const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
|
|
92
|
+
for (const diagnostic of syntacticDiagnostics) {
|
|
93
|
+
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
94
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
95
|
+
errors.push({
|
|
96
|
+
line: line + 1,
|
|
97
|
+
column: character + 1,
|
|
98
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
99
|
+
code: diagnostic.code,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
errors.push({
|
|
104
|
+
line: 0,
|
|
105
|
+
column: 0,
|
|
106
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
107
|
+
code: diagnostic.code,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
valid: errors.length === 0,
|
|
113
|
+
errors,
|
|
114
|
+
filePath: absolutePath,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function formatValidationErrors(result) {
|
|
118
|
+
if (result.valid) {
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
const lines = [];
|
|
122
|
+
const relativePath = path.relative(process.cwd(), result.filePath);
|
|
123
|
+
for (const error of result.errors) {
|
|
124
|
+
if (error.line > 0) {
|
|
125
|
+
lines.push(` ${relativePath}:${error.line}:${error.column}`);
|
|
126
|
+
lines.push(` │`);
|
|
127
|
+
try {
|
|
128
|
+
const content = fs.readFileSync(result.filePath, 'utf-8');
|
|
129
|
+
const fileLine = content.split('\n')[error.line - 1];
|
|
130
|
+
if (fileLine !== undefined) {
|
|
131
|
+
const trimmedLine = fileLine.substring(0, 60);
|
|
132
|
+
lines.push(`${error.line.toString().padStart(3)} │ ${trimmedLine}${fileLine.length > 60 ? '...' : ''}`);
|
|
133
|
+
lines.push(` │ ${' '.repeat(error.column - 1)}^`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
}
|
|
138
|
+
lines.push(` │`);
|
|
139
|
+
lines.push(` └─ ${error.message}`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
lines.push(` ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
lines.push('');
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
@@ -333,9 +333,10 @@ var require_postgres_interval = __commonJS((exports, module) => {
|
|
|
333
333
|
|
|
334
334
|
// node_modules/postgres-bytea/index.js
|
|
335
335
|
var require_postgres_bytea = __commonJS((exports, module) => {
|
|
336
|
+
var bufferFrom = Buffer.from || Buffer;
|
|
336
337
|
module.exports = function parseBytea(input) {
|
|
337
338
|
if (/^\\x/.test(input)) {
|
|
338
|
-
return
|
|
339
|
+
return bufferFrom(input.substr(2), "hex");
|
|
339
340
|
}
|
|
340
341
|
var output = "";
|
|
341
342
|
var i = 0;
|
|
@@ -359,7 +360,7 @@ var require_postgres_bytea = __commonJS((exports, module) => {
|
|
|
359
360
|
}
|
|
360
361
|
}
|
|
361
362
|
}
|
|
362
|
-
return
|
|
363
|
+
return bufferFrom(output, "binary");
|
|
363
364
|
};
|
|
364
365
|
});
|
|
365
366
|
|
|
@@ -324,9 +324,10 @@ var require_postgres_interval = __commonJS((exports, module) => {
|
|
|
324
324
|
|
|
325
325
|
// node_modules/postgres-bytea/index.js
|
|
326
326
|
var require_postgres_bytea = __commonJS((exports, module) => {
|
|
327
|
+
var bufferFrom = Buffer.from || Buffer;
|
|
327
328
|
module.exports = function parseBytea(input) {
|
|
328
329
|
if (/^\\x/.test(input)) {
|
|
329
|
-
return
|
|
330
|
+
return bufferFrom(input.substr(2), "hex");
|
|
330
331
|
}
|
|
331
332
|
var output = "";
|
|
332
333
|
var i = 0;
|
|
@@ -350,7 +351,7 @@ var require_postgres_bytea = __commonJS((exports, module) => {
|
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
353
|
}
|
|
353
|
-
return
|
|
354
|
+
return bufferFrom(output, "binary");
|
|
354
355
|
};
|
|
355
356
|
});
|
|
356
357
|
|
|
@@ -7,6 +7,7 @@ import * as path from 'path';
|
|
|
7
7
|
import { isInitialized, loadSnapshot, getUnstagedChanges, getStagedChanges, stageChanges, detectFileChanges, addUnstagedChanges, clearUnstagedChanges, cleanupStagedChanges, } from "../utils/repo-manager.js";
|
|
8
8
|
import { getChangeDisplayName } from "../utils/change-tracker.js";
|
|
9
9
|
import { compareSchemas } from "../utils/schema-comparator.js";
|
|
10
|
+
import { getSchemaPath } from "../utils/config-loader.js";
|
|
10
11
|
function parseSchemaFileForComparison(schemaPath) {
|
|
11
12
|
if (!fs.existsSync(schemaPath)) {
|
|
12
13
|
return null;
|
|
@@ -583,8 +584,10 @@ export async function addCommand(context) {
|
|
|
583
584
|
}
|
|
584
585
|
const ignorePatterns = loadRelqignore(projectRoot);
|
|
585
586
|
const config = await loadConfig();
|
|
586
|
-
const schemaPathRaw =
|
|
587
|
+
const schemaPathRaw = getSchemaPath(config);
|
|
587
588
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
589
|
+
const { requireValidSchema } = await import("../utils/config-loader.js");
|
|
590
|
+
await requireValidSchema(schemaPath, context.flags);
|
|
588
591
|
const injectedCount = injectTrackingIds(schemaPath);
|
|
589
592
|
if (injectedCount > 0) {
|
|
590
593
|
console.log(colors.muted(`Injected ${injectedCount} tracking ID(s) into schema.ts`));
|
|
@@ -2,6 +2,7 @@ import * as crypto from 'crypto';
|
|
|
2
2
|
import { fatal, hint } from "../utils/cli-utils.js";
|
|
3
3
|
import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, shortHash, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
|
|
4
4
|
import { loadConfig } from "../../config/config.js";
|
|
5
|
+
import { getSchemaPath } from "../utils/config-loader.js";
|
|
5
6
|
import { sortChangesByDependency, generateCombinedSQL, } from "../utils/change-tracker.js";
|
|
6
7
|
import * as fs from 'fs';
|
|
7
8
|
import * as path from 'path';
|
|
@@ -12,6 +13,9 @@ export async function commitCommand(context) {
|
|
|
12
13
|
if (!isInitialized(projectRoot)) {
|
|
13
14
|
fatal('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
|
|
14
15
|
}
|
|
16
|
+
const schemaPath = path.resolve(projectRoot, getSchemaPath(config ?? undefined));
|
|
17
|
+
const { requireValidSchema } = await import("../utils/config-loader.js");
|
|
18
|
+
await requireValidSchema(schemaPath, flags);
|
|
15
19
|
const staged = getStagedChanges(projectRoot);
|
|
16
20
|
if (staged.length === 0) {
|
|
17
21
|
console.log('nothing to commit, working tree clean');
|
|
@@ -86,7 +90,7 @@ export async function commitCommand(context) {
|
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
const commitConfig = await loadConfig();
|
|
89
|
-
const schemaPathRaw =
|
|
93
|
+
const schemaPathRaw = getSchemaPath(commitConfig);
|
|
90
94
|
const schemaFilePath = path.resolve(projectRoot, schemaPathRaw);
|
|
91
95
|
if (fs.existsSync(schemaFilePath)) {
|
|
92
96
|
const currentContent = fs.readFileSync(schemaFilePath, 'utf-8');
|
|
@@ -6,11 +6,13 @@ import { parseSQL, normalizedToParsedSchema } from "../utils/ast-transformer.js"
|
|
|
6
6
|
import { saveSnapshot, loadSnapshot, isInitialized, initRepository, stageChanges, addUnstagedChanges } from "../utils/repo-manager.js";
|
|
7
7
|
import { compareSchemas } from "../utils/schema-comparator.js";
|
|
8
8
|
import { getChangeDisplayName } from "../utils/change-tracker.js";
|
|
9
|
+
import { getSchemaPath, loadConfigWithEnv } from "../utils/config-loader.js";
|
|
9
10
|
import { loadRelqignore, validateIgnoreDependencies, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
10
11
|
import { colors, fatal, warning, hint, getWorkingTreeStatus, printDirtyWorkingTreeError, printMergeStrategyHelp, readSQLFile, createSpinner, formatBytes, } from "../utils/git-utils.js";
|
|
11
12
|
export async function importCommand(sqlFilePath, options = {}, projectRoot = process.cwd()) {
|
|
12
13
|
const { includeFunctions = false, includeTriggers = false, force = false, dryRun = false } = options;
|
|
13
14
|
const spinner = createSpinner();
|
|
15
|
+
const config = await loadConfigWithEnv();
|
|
14
16
|
console.log('');
|
|
15
17
|
if (!sqlFilePath) {
|
|
16
18
|
fatal('No SQL file specified', 'usage: relq import <sql-file> [options]\n\n' +
|
|
@@ -360,7 +362,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
360
362
|
console.log(`${colors.yellow('Dry run mode')} - no files written`);
|
|
361
363
|
console.log('');
|
|
362
364
|
console.log('Would write:');
|
|
363
|
-
const dryRunOutputPath = options.output ||
|
|
365
|
+
const dryRunOutputPath = options.output || getSchemaPath(config);
|
|
364
366
|
const dryRunAbsPath = path.resolve(projectRoot, dryRunOutputPath);
|
|
365
367
|
console.log(` ${colors.cyan(dryRunAbsPath)} ${colors.gray(`(${formatBytes(finalTypescriptContent.length)})`)}`);
|
|
366
368
|
console.log(` ${colors.cyan(path.join(projectRoot, '.relq/snapshot.json'))}`);
|
|
@@ -370,7 +372,7 @@ export async function importCommand(sqlFilePath, options = {}, projectRoot = pro
|
|
|
370
372
|
console.log('');
|
|
371
373
|
return;
|
|
372
374
|
}
|
|
373
|
-
const outputPath = options.output ||
|
|
375
|
+
const outputPath = options.output || getSchemaPath(config);
|
|
374
376
|
const absoluteOutputPath = path.resolve(projectRoot, outputPath);
|
|
375
377
|
const outputDir = path.dirname(absoluteOutputPath);
|
|
376
378
|
if (!fs.existsSync(outputDir)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { requireValidConfig } from "../utils/config-loader.js";
|
|
3
|
+
import { requireValidConfig, getSchemaPath } from "../utils/config-loader.js";
|
|
4
4
|
import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
5
5
|
import { introspectedToParsedSchema } from "../utils/ast-transformer.js";
|
|
6
6
|
import { generateTypeScriptFromAST } from "../utils/ast-codegen.js";
|
|
@@ -144,7 +144,7 @@ export async function pullCommand(context) {
|
|
|
144
144
|
const noCommit = flags['no-commit'] === true;
|
|
145
145
|
const dryRun = flags['dry-run'] === true;
|
|
146
146
|
const author = config.author || 'Relq CLI';
|
|
147
|
-
const schemaPathRaw =
|
|
147
|
+
const schemaPathRaw = getSchemaPath(config);
|
|
148
148
|
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
149
149
|
const includeFunctions = config.includeFunctions ?? true;
|
|
150
150
|
const includeTriggers = config.includeTriggers ?? true;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { getSchemaPath } from "../utils/config-loader.js";
|
|
3
|
+
import { validateSchemaFile, formatValidationErrors } from "../utils/schema-validator.js";
|
|
4
|
+
import { colors, success } from "../utils/cli-utils.js";
|
|
5
|
+
export async function validateCommand(context) {
|
|
6
|
+
const { config, args, projectRoot } = context;
|
|
7
|
+
console.log('');
|
|
8
|
+
let schemaPath;
|
|
9
|
+
if (args.length > 0) {
|
|
10
|
+
schemaPath = path.resolve(projectRoot, args[0]);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
schemaPath = path.resolve(projectRoot, getSchemaPath(config ?? undefined));
|
|
14
|
+
}
|
|
15
|
+
const relativePath = path.relative(process.cwd(), schemaPath);
|
|
16
|
+
console.log(`Validating ${colors.cyan(relativePath)}...`);
|
|
17
|
+
console.log('');
|
|
18
|
+
const result = validateSchemaFile(schemaPath);
|
|
19
|
+
if (result.valid) {
|
|
20
|
+
success('Schema is valid');
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(` No syntax errors found in ${relativePath}`);
|
|
23
|
+
console.log('');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.log(colors.red(`error: Schema has ${result.errors.length} syntax error(s)`));
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(formatValidationErrors(result));
|
|
29
|
+
console.log(colors.yellow('hint:') + ' Fix the errors above before running other commands');
|
|
30
|
+
console.log('');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
export default validateCommand;
|
package/dist/esm/cli/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { mergeCommand } from "./commands/merge.js";
|
|
|
21
21
|
import { tagCommand } from "./commands/tag.js";
|
|
22
22
|
import { cherryPickCommand } from "./commands/cherry-pick.js";
|
|
23
23
|
import { remoteCommand } from "./commands/remote.js";
|
|
24
|
+
import { validateCommand } from "./commands/validate.js";
|
|
24
25
|
import * as fs from 'fs';
|
|
25
26
|
import * as path from 'path';
|
|
26
27
|
function loadEnvFile() {
|
|
@@ -125,6 +126,7 @@ Sync Commands:
|
|
|
125
126
|
sync Pull + push in one command
|
|
126
127
|
|
|
127
128
|
Other Commands:
|
|
129
|
+
validate Check schema for errors
|
|
128
130
|
diff [--sql] Show schema differences
|
|
129
131
|
generate Generate TypeScript types
|
|
130
132
|
introspect Parse database schema
|
|
@@ -157,7 +159,7 @@ function printVersion() {
|
|
|
157
159
|
console.log(`relq v${VERSION}`);
|
|
158
160
|
}
|
|
159
161
|
function requiresConfig(command) {
|
|
160
|
-
return !['init', 'introspect', 'import', 'help', 'version'].includes(command);
|
|
162
|
+
return !['init', 'introspect', 'import', 'help', 'version', 'validate'].includes(command);
|
|
161
163
|
}
|
|
162
164
|
function requiresDbConnection(command, flags) {
|
|
163
165
|
const alwaysNeedDb = ['pull', 'push', 'fetch', 'introspect'];
|
|
@@ -319,6 +321,9 @@ async function main() {
|
|
|
319
321
|
case 'introspect':
|
|
320
322
|
await introspectCommand(context);
|
|
321
323
|
break;
|
|
324
|
+
case 'validate':
|
|
325
|
+
await validateCommand(context);
|
|
326
|
+
break;
|
|
322
327
|
case 'sync':
|
|
323
328
|
await syncCommand(context);
|
|
324
329
|
break;
|
|
@@ -73,6 +73,23 @@ export function validateConfig(config) {
|
|
|
73
73
|
}
|
|
74
74
|
return errors;
|
|
75
75
|
}
|
|
76
|
+
export function getSchemaPath(config, defaultPath = './db/schema.ts') {
|
|
77
|
+
if (!config)
|
|
78
|
+
return defaultPath;
|
|
79
|
+
if (typeof config.schema === 'string' && config.schema.length > 0) {
|
|
80
|
+
return config.schema;
|
|
81
|
+
}
|
|
82
|
+
if (typeof config.schema === 'object' && config.schema?.directory) {
|
|
83
|
+
return `${config.schema.directory}/schema.ts`;
|
|
84
|
+
}
|
|
85
|
+
if (config.generate?.outDir) {
|
|
86
|
+
return `${config.generate.outDir}/schema.ts`;
|
|
87
|
+
}
|
|
88
|
+
if (config.typeGeneration?.output) {
|
|
89
|
+
return config.typeGeneration.output;
|
|
90
|
+
}
|
|
91
|
+
return defaultPath;
|
|
92
|
+
}
|
|
76
93
|
export async function requireValidConfig(config, options) {
|
|
77
94
|
const errors = validateConfig(config);
|
|
78
95
|
if (errors.length === 0)
|
|
@@ -110,3 +127,25 @@ export async function requireValidConfig(config, options) {
|
|
|
110
127
|
console.error('');
|
|
111
128
|
process.exit(1);
|
|
112
129
|
}
|
|
130
|
+
export async function requireValidSchema(schemaPath, flags = {}) {
|
|
131
|
+
if (flags['skip-validation'] === true) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
const { validateSchemaFile, formatValidationErrors } = await import("./schema-validator.js");
|
|
135
|
+
const result = validateSchemaFile(schemaPath);
|
|
136
|
+
if (result.valid) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
const colors = {
|
|
140
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
141
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
142
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
143
|
+
};
|
|
144
|
+
console.error('');
|
|
145
|
+
console.error(colors.red('error:') + ` Schema has ${result.errors.length} syntax error(s)`);
|
|
146
|
+
console.error('');
|
|
147
|
+
console.error(formatValidationErrors(result));
|
|
148
|
+
console.error(colors.yellow('hint:') + ` Fix the errors above, or use ${colors.cyan('--skip-validation')} to bypass`);
|
|
149
|
+
console.error('');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
export function validateSchemaFile(schemaPath) {
|
|
5
|
+
const absolutePath = path.resolve(schemaPath);
|
|
6
|
+
if (!fs.existsSync(absolutePath)) {
|
|
7
|
+
return {
|
|
8
|
+
valid: false,
|
|
9
|
+
errors: [{
|
|
10
|
+
line: 0,
|
|
11
|
+
column: 0,
|
|
12
|
+
message: `Schema file not found: ${absolutePath}`,
|
|
13
|
+
code: -1,
|
|
14
|
+
}],
|
|
15
|
+
filePath: absolutePath,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const content = fs.readFileSync(absolutePath, 'utf-8');
|
|
19
|
+
const sourceFile = ts.createSourceFile(absolutePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
20
|
+
const errors = [];
|
|
21
|
+
const compilerOptions = {
|
|
22
|
+
target: ts.ScriptTarget.ESNext,
|
|
23
|
+
module: ts.ModuleKind.ESNext,
|
|
24
|
+
strict: false,
|
|
25
|
+
skipLibCheck: true,
|
|
26
|
+
noEmit: true,
|
|
27
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
28
|
+
};
|
|
29
|
+
const program = ts.createProgram([absolutePath], compilerOptions, {
|
|
30
|
+
getSourceFile: (fileName) => {
|
|
31
|
+
if (fileName === absolutePath)
|
|
32
|
+
return sourceFile;
|
|
33
|
+
return ts.createSourceFile(fileName, '', ts.ScriptTarget.Latest, true);
|
|
34
|
+
},
|
|
35
|
+
getDefaultLibFileName: () => 'lib.d.ts',
|
|
36
|
+
writeFile: () => { },
|
|
37
|
+
getCurrentDirectory: () => path.dirname(absolutePath),
|
|
38
|
+
getDirectories: () => [],
|
|
39
|
+
fileExists: (fileName) => fileName === absolutePath || fs.existsSync(fileName),
|
|
40
|
+
readFile: (fileName) => {
|
|
41
|
+
if (fileName === absolutePath)
|
|
42
|
+
return content;
|
|
43
|
+
try {
|
|
44
|
+
return fs.readFileSync(fileName, 'utf-8');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
51
|
+
useCaseSensitiveFileNames: () => true,
|
|
52
|
+
getNewLine: () => '\n',
|
|
53
|
+
});
|
|
54
|
+
const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
|
|
55
|
+
for (const diagnostic of syntacticDiagnostics) {
|
|
56
|
+
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
57
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
58
|
+
errors.push({
|
|
59
|
+
line: line + 1,
|
|
60
|
+
column: character + 1,
|
|
61
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
62
|
+
code: diagnostic.code,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
errors.push({
|
|
67
|
+
line: 0,
|
|
68
|
+
column: 0,
|
|
69
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
70
|
+
code: diagnostic.code,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
valid: errors.length === 0,
|
|
76
|
+
errors,
|
|
77
|
+
filePath: absolutePath,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function formatValidationErrors(result) {
|
|
81
|
+
if (result.valid) {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
const lines = [];
|
|
85
|
+
const relativePath = path.relative(process.cwd(), result.filePath);
|
|
86
|
+
for (const error of result.errors) {
|
|
87
|
+
if (error.line > 0) {
|
|
88
|
+
lines.push(` ${relativePath}:${error.line}:${error.column}`);
|
|
89
|
+
lines.push(` │`);
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(result.filePath, 'utf-8');
|
|
92
|
+
const fileLine = content.split('\n')[error.line - 1];
|
|
93
|
+
if (fileLine !== undefined) {
|
|
94
|
+
const trimmedLine = fileLine.substring(0, 60);
|
|
95
|
+
lines.push(`${error.line.toString().padStart(3)} │ ${trimmedLine}${fileLine.length > 60 ? '...' : ''}`);
|
|
96
|
+
lines.push(` │ ${' '.repeat(error.column - 1)}^`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
}
|
|
101
|
+
lines.push(` │`);
|
|
102
|
+
lines.push(` └─ ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
lines.push(` ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|