stepwise-migrations 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +74 -0
- package/dist/db.js +103 -0
- package/dist/index.js +62 -0
- package/dist/migrate.js +62 -0
- package/dist/types.js +2 -0
- package/dist/utils.js +86 -0
- package/package.json +19 -0
- package/src/db.ts +79 -0
- package/src/index.ts +71 -0
- package/src/migrate.ts +75 -0
- package/src/types.ts +7 -0
- package/src/utils.ts +80 -0
- package/tsconfig.json +111 -0
package/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Stepwise Migrations
|
2
|
+
|
3
|
+
A tool for managing migrations in a Postgres database.
|
4
|
+
Loosely based, but in Typescript.
|
5
|
+
|
6
|
+
Only "up" migrations are supported so far, but what more do you need?
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
### Migrate
|
11
|
+
|
12
|
+
```bash
|
13
|
+
npx stepwise-migrations migrate \
|
14
|
+
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydb \
|
15
|
+
--schema=myschema \
|
16
|
+
--path=./db/migration/
|
17
|
+
```
|
18
|
+
|
19
|
+
Outputs:
|
20
|
+
|
21
|
+
```
|
22
|
+
Connected to the database
|
23
|
+
Creating schema collie
|
24
|
+
Schema collie created
|
25
|
+
Creating migration history table
|
26
|
+
Migration history table created
|
27
|
+
Found 2 migration files
|
28
|
+
Applied migration V0_01__connect_session_table.sql
|
29
|
+
Applied migration V0_02__auth.sql
|
30
|
+
|
31
|
+
All done!
|
32
|
+
```
|
33
|
+
|
34
|
+
### Info
|
35
|
+
|
36
|
+
```bash
|
37
|
+
npx stepwise-migrations info \
|
38
|
+
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydb \
|
39
|
+
--schema=myschema \
|
40
|
+
--path=./db/migration/
|
41
|
+
```
|
42
|
+
|
43
|
+
Outputs:
|
44
|
+
|
45
|
+
```
|
46
|
+
Connected to the database
|
47
|
+
Showing information about the current state of the migrations in the database
|
48
|
+
Migration history schema exists
|
49
|
+
Migration history table exists
|
50
|
+
Migration history:
|
51
|
+
┌─────────┬────┬────────────────────────────────────┬────────────────────────────────────────────────────────────────────┬────────────┬──────────────────────────────┐
|
52
|
+
│ (index) │ id │ name │ hash │ applied_by │ applied_at │
|
53
|
+
├─────────┼────┼────────────────────────────────────┼────────────────────────────────────────────────────────────────────┼────────────┼──────────────────────────────┤
|
54
|
+
│ 0 │ 1 │ 'V0_01__connect_session_table.sql' │ 'f08638e58139ae0e2dda24b1bdba29f3f2128597066a23d2bb382d448bbe9d7e' │ 'postgres' │ '2024-11-23 16:24:50.437496' │
|
55
|
+
│ 1 │ 2 │ 'V0_02__auth.sql' │ '0a4c5df39f03df85cb68ef0b297b913d7c15477fa9dcba13b6e0577d88258a8e' │ 'postgres' │ '2024-11-23 16:24:50.440493' │
|
56
|
+
└─────────┴────┴────────────────────────────────────┴────────────────────────────────────────────────────────────────────┴────────────┴──────────────────────────────┘
|
57
|
+
```
|
58
|
+
|
59
|
+
### Drop
|
60
|
+
|
61
|
+
```bash
|
62
|
+
npx stepwise-migrations drop \
|
63
|
+
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydb \
|
64
|
+
--schema=myschema
|
65
|
+
```
|
66
|
+
|
67
|
+
Outputs:
|
68
|
+
|
69
|
+
```
|
70
|
+
Connected to the database
|
71
|
+
Dropping the tables, schema and migration history table
|
72
|
+
|
73
|
+
All done!
|
74
|
+
```
|
package/dist/db.js
ADDED
@@ -0,0 +1,103 @@
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
42
|
+
});
|
43
|
+
};
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
45
|
+
exports.dbCreateHistoryTable = exports.dbCreateSchema = exports.dbMigrationHistory = exports.dbTableExists = exports.dbHistorySchemaExists = exports.dbConnect = void 0;
|
46
|
+
const pg_1 = __importStar(require("pg"));
|
47
|
+
pg_1.default.types.setTypeParser(1114, function (stringValue) {
|
48
|
+
return stringValue; //1114 for time without timezone type
|
49
|
+
});
|
50
|
+
pg_1.default.types.setTypeParser(1082, function (stringValue) {
|
51
|
+
return stringValue; //1082 for date type
|
52
|
+
});
|
53
|
+
const dbConnect = (argv) => __awaiter(void 0, void 0, void 0, function* () {
|
54
|
+
const pool = new pg_1.Pool({
|
55
|
+
connectionString: argv.connection,
|
56
|
+
ssl: argv.ssl === "true",
|
57
|
+
});
|
58
|
+
let client;
|
59
|
+
try {
|
60
|
+
client = yield pool.connect();
|
61
|
+
yield client.query("SELECT 1");
|
62
|
+
console.log("Connected to the database");
|
63
|
+
}
|
64
|
+
catch (error) {
|
65
|
+
console.error("Failed to connect to the database", error);
|
66
|
+
process.exit(1);
|
67
|
+
}
|
68
|
+
return client;
|
69
|
+
});
|
70
|
+
exports.dbConnect = dbConnect;
|
71
|
+
const dbHistorySchemaExists = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
72
|
+
const result = yield client.query(`SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = '${schema}')`);
|
73
|
+
return result.rows[0].exists;
|
74
|
+
});
|
75
|
+
exports.dbHistorySchemaExists = dbHistorySchemaExists;
|
76
|
+
const dbTableExists = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
77
|
+
const tableExistsResult = yield client.query(`SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stepwise_migrations' and schemaname = '${schema}')`);
|
78
|
+
return tableExistsResult.rows[0].exists;
|
79
|
+
});
|
80
|
+
exports.dbTableExists = dbTableExists;
|
81
|
+
const dbMigrationHistory = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
82
|
+
const migrationsQuery = yield client.query(`SELECT * FROM ${schema}.stepwise_migrations`);
|
83
|
+
return migrationsQuery.rows;
|
84
|
+
});
|
85
|
+
exports.dbMigrationHistory = dbMigrationHistory;
|
86
|
+
const dbCreateSchema = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
87
|
+
console.log(`Creating schema ${schema}`);
|
88
|
+
yield client.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
89
|
+
console.log(`Schema ${schema} created`);
|
90
|
+
});
|
91
|
+
exports.dbCreateSchema = dbCreateSchema;
|
92
|
+
const dbCreateHistoryTable = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
93
|
+
console.log(`Creating migration history table`);
|
94
|
+
yield client.query(`CREATE TABLE IF NOT EXISTS ${schema}.stepwise_migrations (
|
95
|
+
id SERIAL PRIMARY KEY,
|
96
|
+
name TEXT NOT NULL,
|
97
|
+
hash TEXT NOT NULL,
|
98
|
+
applied_by TEXT NOT NULL DEFAULT current_user,
|
99
|
+
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
100
|
+
)`);
|
101
|
+
console.log(`Migration history table created`);
|
102
|
+
});
|
103
|
+
exports.dbCreateHistoryTable = dbCreateHistoryTable;
|
package/dist/index.js
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
"use strict";
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
10
|
+
});
|
11
|
+
};
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
14
|
+
};
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
16
|
+
const yargs_1 = __importDefault(require("yargs"));
|
17
|
+
const db_1 = require("./db");
|
18
|
+
const migrate_1 = require("./migrate");
|
19
|
+
const utils_1 = require("./utils");
|
20
|
+
const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
21
|
+
const argv = (0, yargs_1.default)(process.argv.slice(2)).argv;
|
22
|
+
(0, utils_1.validateArgs)(argv);
|
23
|
+
const schema = argv.schema;
|
24
|
+
const command = argv._[0];
|
25
|
+
const client = yield (0, db_1.dbConnect)(argv);
|
26
|
+
const historySchemaExists = yield (0, db_1.dbHistorySchemaExists)(client, schema);
|
27
|
+
const tableExists = yield (0, db_1.dbTableExists)(client, schema);
|
28
|
+
if (command === "migrate") {
|
29
|
+
if (!historySchemaExists) {
|
30
|
+
yield (0, db_1.dbCreateSchema)(client, schema);
|
31
|
+
}
|
32
|
+
if (!tableExists) {
|
33
|
+
yield (0, db_1.dbCreateHistoryTable)(client, schema);
|
34
|
+
}
|
35
|
+
const migrationHistory = yield (0, db_1.dbMigrationHistory)(client, schema);
|
36
|
+
const migrationFiles = yield (0, utils_1.readMigrationFiles)(argv.path);
|
37
|
+
console.log(`Found ${migrationFiles.length} migration files`);
|
38
|
+
(0, migrate_1.validateMigrationFiles)(migrationFiles, migrationHistory);
|
39
|
+
const migrationsToApply = migrationFiles.slice(migrationHistory.length);
|
40
|
+
for (const { filename, contents, hash } of migrationsToApply) {
|
41
|
+
yield (0, migrate_1.applyMigration)(client, schema, filename, contents, hash);
|
42
|
+
}
|
43
|
+
console.log("\nAll done!");
|
44
|
+
}
|
45
|
+
else if (command === "info") {
|
46
|
+
console.log("Showing information about the current state of the migrations in the database");
|
47
|
+
console.log(historySchemaExists ? "Schema exists" : "Schema does not exist");
|
48
|
+
console.log(tableExists
|
49
|
+
? "Migration history table exists"
|
50
|
+
: "Migration history table does not exist");
|
51
|
+
console.log("Migration history:");
|
52
|
+
console.table(yield (0, db_1.dbMigrationHistory)(client, schema));
|
53
|
+
}
|
54
|
+
else if (command === "drop") {
|
55
|
+
console.log("Dropping the tables, schema and migration history table");
|
56
|
+
yield client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);
|
57
|
+
console.log("\nAll done!");
|
58
|
+
}
|
59
|
+
client.release();
|
60
|
+
process.exit(0);
|
61
|
+
});
|
62
|
+
main();
|
package/dist/migrate.js
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.applyMigration = exports.validateMigrationFiles = void 0;
|
13
|
+
const validateMigrationFiles = (migrationFiles, migrationHistory) => {
|
14
|
+
if (migrationFiles.length === 0) {
|
15
|
+
console.log("No migrations found");
|
16
|
+
process.exit(0);
|
17
|
+
}
|
18
|
+
if (migrationFiles.length < migrationHistory.length) {
|
19
|
+
console.error("Error: migration history is longer than the number of migration files, aborting.");
|
20
|
+
process.exit(1);
|
21
|
+
}
|
22
|
+
if (migrationFiles.length === migrationHistory.length) {
|
23
|
+
console.log("All migrations are already applied");
|
24
|
+
process.exit(0);
|
25
|
+
}
|
26
|
+
for (let i = 0; i < migrationFiles.length; i++) {
|
27
|
+
const { filename, hash: migrationHash } = migrationFiles[i];
|
28
|
+
if (i >= migrationHistory.length) {
|
29
|
+
continue;
|
30
|
+
}
|
31
|
+
if (migrationHistory[i].name !== filename) {
|
32
|
+
console.error(`Error: migration ${filename} has been renamed, aborting.`);
|
33
|
+
process.exit(1);
|
34
|
+
}
|
35
|
+
if (migrationHistory[i].hash !== migrationHash) {
|
36
|
+
console.error(`Error: migration ${filename} has been modified, aborting.`);
|
37
|
+
process.exit(1);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
};
|
41
|
+
exports.validateMigrationFiles = validateMigrationFiles;
|
42
|
+
const applyMigration = (client, schema, filename, contents, hash) => __awaiter(void 0, void 0, void 0, function* () {
|
43
|
+
try {
|
44
|
+
yield client.query("BEGIN");
|
45
|
+
yield client.query(`SET search_path TO ${schema};
|
46
|
+
${contents.toString()}`);
|
47
|
+
yield client.query(`INSERT INTO ${schema}.stepwise_migrations (name, hash) VALUES ($1, $2)`, [filename, hash]);
|
48
|
+
yield client.query("COMMIT");
|
49
|
+
console.log(`Applied migration ${filename}`);
|
50
|
+
}
|
51
|
+
catch (error) {
|
52
|
+
try {
|
53
|
+
yield client.query("ROLLBACK");
|
54
|
+
}
|
55
|
+
catch (error) {
|
56
|
+
console.error("Error rolling back transaction", error);
|
57
|
+
}
|
58
|
+
console.error("Error applying migration", error);
|
59
|
+
process.exit(1);
|
60
|
+
}
|
61
|
+
});
|
62
|
+
exports.applyMigration = applyMigration;
|
package/dist/types.js
ADDED
package/dist/utils.js
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
exports.readMigrationFiles = exports.validateArgs = exports.usage = exports.hashFile = void 0;
|
16
|
+
const crypto_1 = __importDefault(require("crypto"));
|
17
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
18
|
+
const path_1 = __importDefault(require("path"));
|
19
|
+
const hashFile = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
20
|
+
const file = yield promises_1.default.readFile(path);
|
21
|
+
return crypto_1.default.createHash("sha256").update(file).digest("hex");
|
22
|
+
});
|
23
|
+
exports.hashFile = hashFile;
|
24
|
+
exports.usage = `
|
25
|
+
Usage: stepwise-migrations [command] [options]
|
26
|
+
|
27
|
+
Commands:
|
28
|
+
migrate
|
29
|
+
Migrate the database to the latest version
|
30
|
+
info
|
31
|
+
Show information about the current state of the migrations in the database
|
32
|
+
drop
|
33
|
+
Drop the tables, schema and migration history table
|
34
|
+
|
35
|
+
Options:
|
36
|
+
--connection <connection> The connection string to use to connect to the database
|
37
|
+
--schema <schema> The schema to use for the migrations
|
38
|
+
--path <path> The path to the migrations directory
|
39
|
+
--ssl true/false Whether to use SSL for the connection (default: false)
|
40
|
+
|
41
|
+
Example:
|
42
|
+
npx stepwise-migrations \
|
43
|
+
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydatabase \
|
44
|
+
--schema=myschema \
|
45
|
+
--path=./db/migration/ \
|
46
|
+
migrate
|
47
|
+
`;
|
48
|
+
const validateArgs = (argv) => {
|
49
|
+
const required = ["connection", "schema", "path", "_"];
|
50
|
+
if (required.some((key) => !(key in argv))) {
|
51
|
+
console.error("Missing required arguments", required.filter((key) => !(key in argv)));
|
52
|
+
console.log(exports.usage);
|
53
|
+
process.exit(1);
|
54
|
+
}
|
55
|
+
if (argv._.length !== 1) {
|
56
|
+
console.error(`Invalid number of arguments: ${argv._.length}`);
|
57
|
+
console.log(exports.usage);
|
58
|
+
process.exit(1);
|
59
|
+
}
|
60
|
+
if (argv._[0] !== "migrate" && argv._[0] !== "info" && argv._[0] !== "drop") {
|
61
|
+
console.error(`Invalid command: ${argv._[0]}`);
|
62
|
+
console.log(exports.usage);
|
63
|
+
process.exit(1);
|
64
|
+
}
|
65
|
+
};
|
66
|
+
exports.validateArgs = validateArgs;
|
67
|
+
const readMigrationFiles = (directory) => __awaiter(void 0, void 0, void 0, function* () {
|
68
|
+
const files = yield promises_1.default.readdir(directory, { withFileTypes: true });
|
69
|
+
const migrationFiles = files
|
70
|
+
.filter((file) => file.isFile() && file.name.endsWith(".sql"))
|
71
|
+
.map((file) => path_1.default.join(directory, file.name));
|
72
|
+
migrationFiles.sort();
|
73
|
+
const results = [];
|
74
|
+
for (const fullFilePath of migrationFiles) {
|
75
|
+
const hash = yield (0, exports.hashFile)(fullFilePath);
|
76
|
+
const contents = yield promises_1.default.readFile(fullFilePath, "utf8");
|
77
|
+
results.push({
|
78
|
+
fullFilePath,
|
79
|
+
filename: path_1.default.basename(fullFilePath),
|
80
|
+
hash,
|
81
|
+
contents,
|
82
|
+
});
|
83
|
+
}
|
84
|
+
return results;
|
85
|
+
});
|
86
|
+
exports.readMigrationFiles = readMigrationFiles;
|
package/package.json
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"name": "stepwise-migrations",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"build": "tsc"
|
8
|
+
},
|
9
|
+
"keywords": [],
|
10
|
+
"author": "",
|
11
|
+
"license": "ISC",
|
12
|
+
"devDependencies": {
|
13
|
+
"@types/pg": "^8.11.10",
|
14
|
+
"@types/yargs": "^17.0.33"
|
15
|
+
},
|
16
|
+
"bin": {
|
17
|
+
"stepwise-migrations": "./dist/index.js"
|
18
|
+
}
|
19
|
+
}
|
package/src/db.ts
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
import pg, { Pool, PoolClient } from "pg";
|
2
|
+
|
3
|
+
pg.types.setTypeParser(1114, function (stringValue) {
|
4
|
+
return stringValue; //1114 for time without timezone type
|
5
|
+
});
|
6
|
+
|
7
|
+
pg.types.setTypeParser(1082, function (stringValue) {
|
8
|
+
return stringValue; //1082 for date type
|
9
|
+
});
|
10
|
+
|
11
|
+
export const dbConnect = async (argv: { connection: string; ssl?: string }) => {
|
12
|
+
const pool = new Pool({
|
13
|
+
connectionString: argv.connection,
|
14
|
+
ssl: argv.ssl === "true",
|
15
|
+
});
|
16
|
+
|
17
|
+
let client: PoolClient | undefined;
|
18
|
+
try {
|
19
|
+
client = await pool.connect();
|
20
|
+
await client.query("SELECT 1");
|
21
|
+
console.log("Connected to the database");
|
22
|
+
} catch (error) {
|
23
|
+
console.error("Failed to connect to the database", error);
|
24
|
+
process.exit(1);
|
25
|
+
}
|
26
|
+
|
27
|
+
return client;
|
28
|
+
};
|
29
|
+
|
30
|
+
export const dbHistorySchemaExists = async (
|
31
|
+
client: PoolClient,
|
32
|
+
schema: string
|
33
|
+
) => {
|
34
|
+
const result = await client.query(
|
35
|
+
`SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = '${schema}')`
|
36
|
+
);
|
37
|
+
return result.rows[0].exists;
|
38
|
+
};
|
39
|
+
|
40
|
+
export const dbTableExists = async (client: PoolClient, schema: string) => {
|
41
|
+
const tableExistsResult = await client.query(
|
42
|
+
`SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stepwise_migrations' and schemaname = '${schema}')`
|
43
|
+
);
|
44
|
+
|
45
|
+
return tableExistsResult.rows[0].exists;
|
46
|
+
};
|
47
|
+
|
48
|
+
export const dbMigrationHistory = async (
|
49
|
+
client: PoolClient,
|
50
|
+
schema: string
|
51
|
+
) => {
|
52
|
+
const migrationsQuery = await client.query(
|
53
|
+
`SELECT * FROM ${schema}.stepwise_migrations`
|
54
|
+
);
|
55
|
+
return migrationsQuery.rows;
|
56
|
+
};
|
57
|
+
|
58
|
+
export const dbCreateSchema = async (client: PoolClient, schema: string) => {
|
59
|
+
console.log(`Creating schema ${schema}`);
|
60
|
+
await client.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
61
|
+
console.log(`Schema ${schema} created`);
|
62
|
+
};
|
63
|
+
|
64
|
+
export const dbCreateHistoryTable = async (
|
65
|
+
client: PoolClient,
|
66
|
+
schema: string
|
67
|
+
) => {
|
68
|
+
console.log(`Creating migration history table`);
|
69
|
+
await client.query(
|
70
|
+
`CREATE TABLE IF NOT EXISTS ${schema}.stepwise_migrations (
|
71
|
+
id SERIAL PRIMARY KEY,
|
72
|
+
name TEXT NOT NULL,
|
73
|
+
hash TEXT NOT NULL,
|
74
|
+
applied_by TEXT NOT NULL DEFAULT current_user,
|
75
|
+
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
76
|
+
)`
|
77
|
+
);
|
78
|
+
console.log(`Migration history table created`);
|
79
|
+
};
|
package/src/index.ts
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import yargs from "yargs";
|
4
|
+
import {
|
5
|
+
dbConnect,
|
6
|
+
dbCreateHistoryTable,
|
7
|
+
dbCreateSchema,
|
8
|
+
dbHistorySchemaExists,
|
9
|
+
dbMigrationHistory,
|
10
|
+
dbTableExists,
|
11
|
+
} from "./db";
|
12
|
+
import { applyMigration, validateMigrationFiles } from "./migrate";
|
13
|
+
import { readMigrationFiles, validateArgs } from "./utils";
|
14
|
+
|
15
|
+
const main = async () => {
|
16
|
+
const argv: any = yargs(process.argv.slice(2)).argv;
|
17
|
+
|
18
|
+
validateArgs(argv);
|
19
|
+
|
20
|
+
const schema = argv.schema;
|
21
|
+
const command = argv._[0];
|
22
|
+
|
23
|
+
const client = await dbConnect(argv);
|
24
|
+
const historySchemaExists = await dbHistorySchemaExists(client, schema);
|
25
|
+
const tableExists = await dbTableExists(client, schema);
|
26
|
+
|
27
|
+
if (command === "migrate") {
|
28
|
+
if (!historySchemaExists) {
|
29
|
+
await dbCreateSchema(client, schema);
|
30
|
+
}
|
31
|
+
if (!tableExists) {
|
32
|
+
await dbCreateHistoryTable(client, schema);
|
33
|
+
}
|
34
|
+
|
35
|
+
const migrationHistory = await dbMigrationHistory(client, schema);
|
36
|
+
const migrationFiles = await readMigrationFiles(argv.path);
|
37
|
+
console.log(`Found ${migrationFiles.length} migration files`);
|
38
|
+
|
39
|
+
validateMigrationFiles(migrationFiles, migrationHistory);
|
40
|
+
|
41
|
+
const migrationsToApply = migrationFiles.slice(migrationHistory.length);
|
42
|
+
|
43
|
+
for (const { filename, contents, hash } of migrationsToApply) {
|
44
|
+
await applyMigration(client, schema, filename, contents, hash);
|
45
|
+
}
|
46
|
+
console.log("\nAll done!");
|
47
|
+
} else if (command === "info") {
|
48
|
+
console.log(
|
49
|
+
"Showing information about the current state of the migrations in the database"
|
50
|
+
);
|
51
|
+
console.log(
|
52
|
+
historySchemaExists ? "Schema exists" : "Schema does not exist"
|
53
|
+
);
|
54
|
+
console.log(
|
55
|
+
tableExists
|
56
|
+
? "Migration history table exists"
|
57
|
+
: "Migration history table does not exist"
|
58
|
+
);
|
59
|
+
console.log("Migration history:");
|
60
|
+
console.table(await dbMigrationHistory(client, schema));
|
61
|
+
} else if (command === "drop") {
|
62
|
+
console.log("Dropping the tables, schema and migration history table");
|
63
|
+
await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);
|
64
|
+
console.log("\nAll done!");
|
65
|
+
}
|
66
|
+
|
67
|
+
client.release();
|
68
|
+
process.exit(0);
|
69
|
+
};
|
70
|
+
|
71
|
+
main();
|
package/src/migrate.ts
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
import { PoolClient } from "pg";
|
2
|
+
import { MigrationRow } from "./types";
|
3
|
+
|
4
|
+
export const validateMigrationFiles = (
|
5
|
+
migrationFiles: { fullFilePath: string; filename: string; hash: string }[],
|
6
|
+
migrationHistory: MigrationRow[]
|
7
|
+
) => {
|
8
|
+
if (migrationFiles.length === 0) {
|
9
|
+
console.log("No migrations found");
|
10
|
+
process.exit(0);
|
11
|
+
}
|
12
|
+
|
13
|
+
if (migrationFiles.length < migrationHistory.length) {
|
14
|
+
console.error(
|
15
|
+
"Error: migration history is longer than the number of migration files, aborting."
|
16
|
+
);
|
17
|
+
process.exit(1);
|
18
|
+
}
|
19
|
+
|
20
|
+
if (migrationFiles.length === migrationHistory.length) {
|
21
|
+
console.log("All migrations are already applied");
|
22
|
+
process.exit(0);
|
23
|
+
}
|
24
|
+
|
25
|
+
for (let i = 0; i < migrationFiles.length; i++) {
|
26
|
+
const { filename, hash: migrationHash } = migrationFiles[i];
|
27
|
+
if (i >= migrationHistory.length) {
|
28
|
+
continue;
|
29
|
+
}
|
30
|
+
if (migrationHistory[i].name !== filename) {
|
31
|
+
console.error(`Error: migration ${filename} has been renamed, aborting.`);
|
32
|
+
process.exit(1);
|
33
|
+
}
|
34
|
+
if (migrationHistory[i].hash !== migrationHash) {
|
35
|
+
console.error(
|
36
|
+
`Error: migration ${filename} has been modified, aborting.`
|
37
|
+
);
|
38
|
+
process.exit(1);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
export const applyMigration = async (
|
44
|
+
client: PoolClient,
|
45
|
+
schema: string,
|
46
|
+
filename: string,
|
47
|
+
contents: string,
|
48
|
+
hash: string
|
49
|
+
) => {
|
50
|
+
try {
|
51
|
+
await client.query("BEGIN");
|
52
|
+
|
53
|
+
await client.query(
|
54
|
+
`SET search_path TO ${schema};
|
55
|
+
${contents.toString()}`
|
56
|
+
);
|
57
|
+
|
58
|
+
await client.query(
|
59
|
+
`INSERT INTO ${schema}.stepwise_migrations (name, hash) VALUES ($1, $2)`,
|
60
|
+
[filename, hash]
|
61
|
+
);
|
62
|
+
|
63
|
+
await client.query("COMMIT");
|
64
|
+
|
65
|
+
console.log(`Applied migration ${filename}`);
|
66
|
+
} catch (error) {
|
67
|
+
try {
|
68
|
+
await client.query("ROLLBACK");
|
69
|
+
} catch (error) {
|
70
|
+
console.error("Error rolling back transaction", error);
|
71
|
+
}
|
72
|
+
console.error("Error applying migration", error);
|
73
|
+
process.exit(1);
|
74
|
+
}
|
75
|
+
};
|
package/src/types.ts
ADDED
package/src/utils.ts
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
import crypto from "crypto";
|
2
|
+
import fs from "fs/promises";
|
3
|
+
import path from "path";
|
4
|
+
|
5
|
+
export const hashFile = async (path: string) => {
|
6
|
+
const file = await fs.readFile(path);
|
7
|
+
return crypto.createHash("sha256").update(file).digest("hex");
|
8
|
+
};
|
9
|
+
|
10
|
+
export const usage = `
|
11
|
+
Usage: stepwise-migrations [command] [options]
|
12
|
+
|
13
|
+
Commands:
|
14
|
+
migrate
|
15
|
+
Migrate the database to the latest version
|
16
|
+
info
|
17
|
+
Show information about the current state of the migrations in the database
|
18
|
+
drop
|
19
|
+
Drop the tables, schema and migration history table
|
20
|
+
|
21
|
+
Options:
|
22
|
+
--connection <connection> The connection string to use to connect to the database
|
23
|
+
--schema <schema> The schema to use for the migrations
|
24
|
+
--path <path> The path to the migrations directory
|
25
|
+
--ssl true/false Whether to use SSL for the connection (default: false)
|
26
|
+
|
27
|
+
Example:
|
28
|
+
npx stepwise-migrations \
|
29
|
+
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydatabase \
|
30
|
+
--schema=myschema \
|
31
|
+
--path=./db/migration/ \
|
32
|
+
migrate
|
33
|
+
`;
|
34
|
+
|
35
|
+
export const validateArgs = (argv: any) => {
|
36
|
+
const required = ["connection", "schema", "path", "_"];
|
37
|
+
if (required.some((key) => !(key in argv))) {
|
38
|
+
console.error(
|
39
|
+
"Missing required arguments",
|
40
|
+
required.filter((key) => !(key in argv))
|
41
|
+
);
|
42
|
+
console.log(usage);
|
43
|
+
process.exit(1);
|
44
|
+
}
|
45
|
+
if (argv._.length !== 1) {
|
46
|
+
console.error(`Invalid number of arguments: ${argv._.length}`);
|
47
|
+
console.log(usage);
|
48
|
+
process.exit(1);
|
49
|
+
}
|
50
|
+
if (argv._[0] !== "migrate" && argv._[0] !== "info" && argv._[0] !== "drop") {
|
51
|
+
console.error(`Invalid command: ${argv._[0]}`);
|
52
|
+
console.log(usage);
|
53
|
+
process.exit(1);
|
54
|
+
}
|
55
|
+
};
|
56
|
+
|
57
|
+
export const readMigrationFiles = async (directory: string) => {
|
58
|
+
const files = await fs.readdir(directory, { withFileTypes: true });
|
59
|
+
const migrationFiles = files
|
60
|
+
.filter((file) => file.isFile() && file.name.endsWith(".sql"))
|
61
|
+
.map((file) => path.join(directory, file.name));
|
62
|
+
migrationFiles.sort();
|
63
|
+
const results: {
|
64
|
+
fullFilePath: string;
|
65
|
+
filename: string;
|
66
|
+
hash: string;
|
67
|
+
contents: string;
|
68
|
+
}[] = [];
|
69
|
+
for (const fullFilePath of migrationFiles) {
|
70
|
+
const hash = await hashFile(fullFilePath);
|
71
|
+
const contents = await fs.readFile(fullFilePath, "utf8");
|
72
|
+
results.push({
|
73
|
+
fullFilePath,
|
74
|
+
filename: path.basename(fullFilePath),
|
75
|
+
hash,
|
76
|
+
contents,
|
77
|
+
});
|
78
|
+
}
|
79
|
+
return results;
|
80
|
+
};
|
package/tsconfig.json
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
/* Visit https://aka.ms/tsconfig to read more about this file */
|
4
|
+
|
5
|
+
/* Projects */
|
6
|
+
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
7
|
+
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
8
|
+
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
9
|
+
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
10
|
+
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
11
|
+
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
12
|
+
|
13
|
+
/* Language and Environment */
|
14
|
+
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
15
|
+
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
16
|
+
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
17
|
+
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
18
|
+
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
19
|
+
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
20
|
+
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
21
|
+
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
22
|
+
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
23
|
+
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
24
|
+
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
25
|
+
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
26
|
+
|
27
|
+
/* Modules */
|
28
|
+
"module": "commonjs", /* Specify what module code is generated. */
|
29
|
+
"rootDir": "./src", /* Specify the root folder within your source files. */
|
30
|
+
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
31
|
+
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
32
|
+
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
33
|
+
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
34
|
+
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
35
|
+
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
36
|
+
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
37
|
+
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
38
|
+
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
39
|
+
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
40
|
+
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
41
|
+
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
42
|
+
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
43
|
+
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
44
|
+
// "resolveJsonModule": true, /* Enable importing .json files. */
|
45
|
+
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
46
|
+
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
47
|
+
|
48
|
+
/* JavaScript Support */
|
49
|
+
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
50
|
+
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
51
|
+
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
52
|
+
|
53
|
+
/* Emit */
|
54
|
+
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
55
|
+
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
56
|
+
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
57
|
+
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
58
|
+
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
59
|
+
// "noEmit": true, /* Disable emitting files from a compilation. */
|
60
|
+
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
61
|
+
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
62
|
+
// "removeComments": true, /* Disable emitting comments. */
|
63
|
+
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
64
|
+
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
65
|
+
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
66
|
+
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
67
|
+
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
68
|
+
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
69
|
+
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
70
|
+
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
71
|
+
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
72
|
+
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
73
|
+
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
74
|
+
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
75
|
+
|
76
|
+
/* Interop Constraints */
|
77
|
+
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
78
|
+
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
79
|
+
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
80
|
+
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
81
|
+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
82
|
+
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
83
|
+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
84
|
+
|
85
|
+
/* Type Checking */
|
86
|
+
"strict": true, /* Enable all strict type-checking options. */
|
87
|
+
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
88
|
+
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
89
|
+
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
90
|
+
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
91
|
+
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
92
|
+
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
93
|
+
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
94
|
+
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
95
|
+
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
96
|
+
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
97
|
+
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
98
|
+
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
99
|
+
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
100
|
+
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
101
|
+
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
102
|
+
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
103
|
+
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
104
|
+
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
105
|
+
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
106
|
+
|
107
|
+
/* Completeness */
|
108
|
+
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
109
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
110
|
+
}
|
111
|
+
}
|