stepwise-migrations 1.0.14 → 1.0.15
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/README.md +20 -20
- package/dist/{db.js → src/db.js} +73 -28
- package/dist/src/index.js +146 -0
- package/dist/src/state.js +123 -0
- package/dist/src/types.js +13 -0
- package/dist/src/utils.js +184 -0
- package/dist/src/validate.js +1 -0
- package/dist/test/index.test.js +129 -0
- package/dist/test/utils.js +51 -0
- package/docker-compose.yml +21 -0
- package/package.json +12 -6
- package/src/db.ts +92 -37
- package/src/index.ts +115 -80
- package/src/state.ts +166 -0
- package/src/types.ts +49 -4
- package/src/utils.ts +122 -66
- package/test/index.test.ts +166 -0
- package/test/migrations-invalid/v0_get_number.repeatable.sql +6 -0
- package/test/migrations-invalid/v1_first.sql +1 -0
- package/test/migrations-invalid/v2_second.sql +4 -0
- package/test/migrations-invalid/v2_second.undo.sql +1 -0
- package/test/migrations-invalid/v3_third.sql +4 -0
- package/test/migrations-invalid/v3_third.undo.sql +1 -0
- package/test/migrations-template/v0_get_number.repeatable.sql +6 -0
- package/test/migrations-template/v1_first.sql +4 -0
- package/test/migrations-template/v2_second.sql +4 -0
- package/test/migrations-template/v2_second.undo.sql +1 -0
- package/test/migrations-template/v3_third.sql +4 -0
- package/test/migrations-template/v3_third.undo.sql +1 -0
- package/test/migrations-valid/v0_get_number.repeatable.sql +8 -0
- package/test/migrations-valid/v1_first.sql +4 -0
- package/test/migrations-valid/v2_second.sql +4 -0
- package/test/migrations-valid/v2_second.undo.sql +1 -0
- package/test/migrations-valid/v3_third.sql +4 -0
- package/test/migrations-valid/v3_third.undo.sql +1 -0
- package/test/utils.ts +69 -0
- package/tsconfig.json +1 -1
- package/dist/index.js +0 -115
- package/dist/migrate.js +0 -102
- package/dist/types.js +0 -2
- package/dist/utils.js +0 -132
- package/src/migrate.ts +0 -143
package/src/utils.ts
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
import fs from "fs/promises";
|
2
2
|
import path from "path";
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
AppliedMigration,
|
5
|
+
MigrationFile,
|
6
|
+
MigrationState,
|
7
|
+
MigrationType,
|
8
|
+
} from "./types";
|
4
9
|
|
5
10
|
export const usage = `
|
6
11
|
Usage: stepwise-migrations [command] [options]
|
@@ -20,13 +25,13 @@ Options:
|
|
20
25
|
--schema <schema> The schema to use for the migrations
|
21
26
|
--path <path> The path to the migrations directory
|
22
27
|
--ssl true/false Whether to use SSL for the connection (default: false)
|
23
|
-
--
|
24
|
-
--
|
28
|
+
--napply Number of up migrations to apply (default: all)
|
29
|
+
--nundo Number of down migrations to apply (default: 1)
|
25
30
|
|
26
31
|
Example:
|
27
|
-
npx stepwise-migrations migrate
|
28
|
-
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydatabase
|
29
|
-
--schema=myschema
|
32
|
+
npx stepwise-migrations migrate \\
|
33
|
+
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydatabase \\
|
34
|
+
--schema=myschema \\
|
30
35
|
--path=./db/migration/
|
31
36
|
`;
|
32
37
|
|
@@ -47,67 +52,117 @@ export const validateArgs = (argv: any) => {
|
|
47
52
|
}
|
48
53
|
};
|
49
54
|
|
50
|
-
export const
|
55
|
+
export const filenameToType = (filename: string): MigrationType => {
|
56
|
+
if (filename.endsWith(".undo.sql")) {
|
57
|
+
return "undo";
|
58
|
+
} else if (filename.endsWith(".repeatable.sql")) {
|
59
|
+
return "repeatable";
|
60
|
+
}
|
61
|
+
return "versioned";
|
62
|
+
};
|
63
|
+
|
64
|
+
export const readMigrationFiles = async (
|
65
|
+
directory: string,
|
66
|
+
appliedVersionedMigrations: AppliedMigration[]
|
67
|
+
) => {
|
68
|
+
let errors: string[] = [];
|
51
69
|
const files = await fs.readdir(directory, { withFileTypes: true });
|
52
70
|
const migrationFiles = files
|
53
|
-
.filter(
|
54
|
-
(file) =>
|
55
|
-
file.isFile() &&
|
56
|
-
file.name.endsWith(".sql") &&
|
57
|
-
!file.name.endsWith(".down.sql")
|
58
|
-
)
|
71
|
+
.filter((file) => file.isFile() && file.name.endsWith(".sql"))
|
59
72
|
.map((file) => path.join(directory, file.name));
|
60
73
|
migrationFiles.sort();
|
61
|
-
const results:
|
62
|
-
|
63
|
-
|
64
|
-
filename: string;
|
65
|
-
script: string;
|
66
|
-
}[] = [];
|
67
|
-
for (const fullFilePath of migrationFiles) {
|
68
|
-
const script = await fs.readFile(fullFilePath, "utf8");
|
69
|
-
|
74
|
+
const results: MigrationFile[] = [];
|
75
|
+
for (const fullFileName of migrationFiles) {
|
76
|
+
const script = await fs.readFile(fullFileName, "utf8");
|
70
77
|
results.push({
|
71
|
-
type:
|
72
|
-
|
73
|
-
filename: path.basename(fullFilePath),
|
78
|
+
type: filenameToType(path.basename(fullFileName)),
|
79
|
+
filename: path.basename(fullFileName),
|
74
80
|
script,
|
75
81
|
});
|
76
82
|
}
|
77
|
-
|
83
|
+
|
84
|
+
for (const appliedMigration of appliedVersionedMigrations) {
|
85
|
+
const file = results.find((f) => f.filename === appliedMigration.filename);
|
86
|
+
if (
|
87
|
+
file &&
|
88
|
+
file.type === "versioned" &&
|
89
|
+
file.script !== appliedMigration.script
|
90
|
+
) {
|
91
|
+
errors.push(
|
92
|
+
`Versioned migration ${appliedMigration.filename} has been altered. Cannot migrate in current state.`
|
93
|
+
);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
return { files: results, errors };
|
78
98
|
};
|
79
99
|
|
80
100
|
export const printMigrationHistoryAndUnappliedMigrations = (
|
81
|
-
|
82
|
-
migrationHistory: MigrationRow[]
|
101
|
+
state: MigrationState
|
83
102
|
) => {
|
84
|
-
console.log("
|
103
|
+
console.log("All applied versioned migrations:");
|
85
104
|
console.table(
|
86
|
-
|
105
|
+
state.current.appliedVersionedMigrations.map((h) => ({
|
87
106
|
id: h.id,
|
88
|
-
|
107
|
+
type: h.type,
|
108
|
+
filename: h.filename,
|
89
109
|
applied_by: h.applied_by,
|
90
110
|
applied_at: h.applied_at,
|
91
111
|
}))
|
92
112
|
);
|
93
|
-
|
113
|
+
if (state.current.appliedRepeatableMigrations.length > 0) {
|
114
|
+
console.log("All applied repeatable migrations:");
|
115
|
+
console.table(
|
116
|
+
state.current.appliedRepeatableMigrations.map((h) => ({
|
117
|
+
id: h.id,
|
118
|
+
type: h.type,
|
119
|
+
filename: h.filename,
|
120
|
+
applied_by: h.applied_by,
|
121
|
+
applied_at: h.applied_at,
|
122
|
+
}))
|
123
|
+
);
|
124
|
+
}
|
125
|
+
console.log("Unapplied versioned migrations:");
|
94
126
|
console.table(
|
95
|
-
|
96
|
-
|
127
|
+
state.files.unappliedVersionedFiles.map((h) => ({
|
128
|
+
type: h.type,
|
129
|
+
filename: h.filename,
|
97
130
|
}))
|
98
131
|
);
|
132
|
+
if (state.files.unappliedRepeatableFiles.length > 0) {
|
133
|
+
console.log("Unapplied repeatable migrations:");
|
134
|
+
console.table(
|
135
|
+
state.files.unappliedRepeatableFiles.map((h) => ({
|
136
|
+
type: h.type,
|
137
|
+
filename: h.filename,
|
138
|
+
}))
|
139
|
+
);
|
140
|
+
}
|
99
141
|
};
|
100
142
|
|
101
|
-
export const printMigrationHistory = (
|
102
|
-
console.log("
|
143
|
+
export const printMigrationHistory = (state: MigrationState) => {
|
144
|
+
console.log("All applied versioned migrations:");
|
103
145
|
console.table(
|
104
|
-
|
146
|
+
state.current.appliedVersionedMigrations.map((h) => ({
|
105
147
|
id: h.id,
|
106
|
-
|
148
|
+
type: h.type,
|
149
|
+
filename: h.filename,
|
107
150
|
applied_by: h.applied_by,
|
108
151
|
applied_at: h.applied_at,
|
109
152
|
}))
|
110
153
|
);
|
154
|
+
if (state.current.appliedRepeatableMigrations.length > 0) {
|
155
|
+
console.log("All applied repeatable migrations:");
|
156
|
+
console.table(
|
157
|
+
state.current.appliedRepeatableMigrations.map((h) => ({
|
158
|
+
id: h.id,
|
159
|
+
type: h.type,
|
160
|
+
filename: h.filename,
|
161
|
+
applied_by: h.applied_by,
|
162
|
+
applied_at: h.applied_at,
|
163
|
+
}))
|
164
|
+
);
|
165
|
+
}
|
111
166
|
};
|
112
167
|
|
113
168
|
export const fileExists = async (path: string) => {
|
@@ -118,35 +173,36 @@ export const fileExists = async (path: string) => {
|
|
118
173
|
}
|
119
174
|
};
|
120
175
|
|
121
|
-
export const
|
122
|
-
|
123
|
-
|
176
|
+
export const abortIfErrors = (state: MigrationState) => {
|
177
|
+
if (state.errors.length > 0) {
|
178
|
+
console.error(
|
179
|
+
`There were errors loading the migration state. Please fix the errors and try again.`
|
180
|
+
);
|
181
|
+
console.error(state.errors.map((e) => " - " + e).join("\n"));
|
182
|
+
process.exit(1);
|
183
|
+
}
|
184
|
+
};
|
185
|
+
|
186
|
+
export const exitIfNotInitialized = (
|
187
|
+
schemaExists: boolean,
|
188
|
+
tableExists: boolean
|
124
189
|
) => {
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
for (const migration of migrationHistory) {
|
134
|
-
const fullFilePath = path.join(
|
135
|
-
directory,
|
136
|
-
`${migration.name.split(".sql")[0]}.down.sql`
|
190
|
+
if (!schemaExists) {
|
191
|
+
console.log("Schema does not exist. Run migrate to begin.");
|
192
|
+
process.exit(1);
|
193
|
+
}
|
194
|
+
|
195
|
+
if (!tableExists) {
|
196
|
+
console.log(
|
197
|
+
"Migration table has not been initialised. Run migrate to begin."
|
137
198
|
);
|
138
|
-
|
139
|
-
console.error(`Down migration file not found: ${fullFilePath}`);
|
140
|
-
process.exit(1);
|
141
|
-
}
|
142
|
-
const script = await fs.readFile(fullFilePath, "utf8");
|
143
|
-
results.push({
|
144
|
-
type: "down",
|
145
|
-
fullFilePath,
|
146
|
-
filename: path.basename(fullFilePath),
|
147
|
-
upFilename: migration.name,
|
148
|
-
script,
|
149
|
-
});
|
199
|
+
process.exit(1);
|
150
200
|
}
|
151
|
-
|
201
|
+
};
|
202
|
+
|
203
|
+
export const sliceFromFirstNull = <T>(array: (T | undefined)[]): T[] => {
|
204
|
+
const indexOfFirstNull = array.findIndex((x) => x == null);
|
205
|
+
return indexOfFirstNull < 0
|
206
|
+
? (array as T[])
|
207
|
+
: (array.slice(0, indexOfFirstNull) as T[]);
|
152
208
|
};
|
@@ -0,0 +1,166 @@
|
|
1
|
+
import assert from "node:assert";
|
2
|
+
import fs from "node:fs";
|
3
|
+
import { beforeEach, describe, it } from "node:test";
|
4
|
+
import { assertIncludesAll, assertIncludesExcludesAll, execute } from "./utils";
|
5
|
+
const connection = "postgresql://postgres:postgres@127.0.0.1:5432/stepwise-db";
|
6
|
+
const schema = "stepwise";
|
7
|
+
|
8
|
+
const paths = {
|
9
|
+
valid: "./test/migrations-valid",
|
10
|
+
invalid: "./test/migrations-invalid",
|
11
|
+
};
|
12
|
+
|
13
|
+
const executeCommand = (
|
14
|
+
command: string,
|
15
|
+
path: string = "",
|
16
|
+
extraArgs: string = ""
|
17
|
+
) =>
|
18
|
+
execute(`npm exec stepwise-migrations ${command} -- \\
|
19
|
+
--connection=${connection} \\
|
20
|
+
--schema=${schema} \\
|
21
|
+
--path=${path} ${extraArgs}
|
22
|
+
`);
|
23
|
+
|
24
|
+
describe("valid migrations", async () => {
|
25
|
+
beforeEach(async () => {
|
26
|
+
const { output, error, exitCode } = await executeCommand("drop", "");
|
27
|
+
assert.ok(
|
28
|
+
output.includes(
|
29
|
+
"Dropping the tables, schema and migration history table... done!"
|
30
|
+
)
|
31
|
+
);
|
32
|
+
assert.ok(exitCode === 0);
|
33
|
+
|
34
|
+
fs.rmSync(paths.valid, { recursive: true, force: true });
|
35
|
+
fs.cpSync("./test/migrations-template", paths.valid, {
|
36
|
+
recursive: true,
|
37
|
+
});
|
38
|
+
});
|
39
|
+
|
40
|
+
it("migrate without params", async () => {
|
41
|
+
assertIncludesAll(await execute("npm exec stepwise-migrations"), ["Usage"]);
|
42
|
+
});
|
43
|
+
|
44
|
+
it("migrate one versioned and undo, redo, undo", async () => {
|
45
|
+
assertIncludesAll(await executeCommand("migrate", paths.valid), [
|
46
|
+
"All done! Applied 4 migrations",
|
47
|
+
]);
|
48
|
+
assertIncludesAll(await executeCommand("status"), [
|
49
|
+
"v0_get_number.repeatable.sql",
|
50
|
+
"v1_first.sql",
|
51
|
+
"v2_second.sql",
|
52
|
+
"v3_third.sql",
|
53
|
+
]);
|
54
|
+
|
55
|
+
assertIncludesAll(await executeCommand("undo", paths.valid), [
|
56
|
+
"All done! Performed 1 undo migration",
|
57
|
+
]);
|
58
|
+
assertIncludesExcludesAll(
|
59
|
+
await executeCommand("status"),
|
60
|
+
["v0_get_number.repeatable.sql", "v1_first.sql", "v2_second.sql"],
|
61
|
+
["v3_third.sql"]
|
62
|
+
);
|
63
|
+
|
64
|
+
assertIncludesAll(await executeCommand("migrate", paths.valid), [
|
65
|
+
"All done! Applied 1 migration",
|
66
|
+
]);
|
67
|
+
assertIncludesAll(await executeCommand("status"), [
|
68
|
+
"v0_get_number.repeatable.sql",
|
69
|
+
"v1_first.sql",
|
70
|
+
"v2_second.sql",
|
71
|
+
"v3_third.sql",
|
72
|
+
]);
|
73
|
+
|
74
|
+
assertIncludesAll(await executeCommand("undo", paths.valid, "--nundo=2"), [
|
75
|
+
"All done! Performed 2 undo migrations",
|
76
|
+
]);
|
77
|
+
assertIncludesExcludesAll(
|
78
|
+
await executeCommand("status"),
|
79
|
+
["v0_get_number.repeatable.sql", "v1_first.sql"],
|
80
|
+
["v2_second.sql", "v3_third.sql"]
|
81
|
+
);
|
82
|
+
|
83
|
+
assertIncludesAll(await executeCommand("migrate", paths.valid), [
|
84
|
+
"All done! Applied 2 migrations",
|
85
|
+
]);
|
86
|
+
assertIncludesAll(await executeCommand("status"), [
|
87
|
+
"v0_get_number.repeatable.sql",
|
88
|
+
"v1_first.sql",
|
89
|
+
"v2_second.sql",
|
90
|
+
"v3_third.sql",
|
91
|
+
]);
|
92
|
+
});
|
93
|
+
|
94
|
+
it("migrate with altered repeatable migration", async () => {
|
95
|
+
assertIncludesAll(await executeCommand("migrate", paths.valid), [
|
96
|
+
"All done! Applied 4 migrations",
|
97
|
+
]);
|
98
|
+
assertIncludesAll(await executeCommand("status"), [
|
99
|
+
"v0_get_number.repeatable.sql",
|
100
|
+
"v1_first.sql",
|
101
|
+
"v2_second.sql",
|
102
|
+
"v3_third.sql",
|
103
|
+
]);
|
104
|
+
|
105
|
+
fs.writeFileSync(
|
106
|
+
"./test/migrations-valid/v0_get_number.repeatable.sql",
|
107
|
+
`
|
108
|
+
CREATE OR REPLACE FUNCTION get_number()
|
109
|
+
RETURNS integer AS $$
|
110
|
+
BEGIN
|
111
|
+
RETURN 2;
|
112
|
+
END; $$
|
113
|
+
LANGUAGE plpgsql;
|
114
|
+
`
|
115
|
+
);
|
116
|
+
|
117
|
+
assertIncludesAll(await executeCommand("migrate", paths.valid), [
|
118
|
+
"All done! Applied 1 migration",
|
119
|
+
]);
|
120
|
+
});
|
121
|
+
});
|
122
|
+
|
123
|
+
describe.only("invalid migrations", async () => {
|
124
|
+
beforeEach(async () => {
|
125
|
+
const { output, error, exitCode } = await executeCommand("drop", "");
|
126
|
+
assert.ok(
|
127
|
+
output.includes(
|
128
|
+
"Dropping the tables, schema and migration history table... done!"
|
129
|
+
)
|
130
|
+
);
|
131
|
+
assert.ok(exitCode === 0);
|
132
|
+
|
133
|
+
fs.rmSync(paths.invalid, { recursive: true, force: true });
|
134
|
+
fs.cpSync("./test/migrations-template", paths.invalid, {
|
135
|
+
recursive: true,
|
136
|
+
});
|
137
|
+
});
|
138
|
+
|
139
|
+
it.only("missing undo migration", async () => {
|
140
|
+
assertIncludesAll(await executeCommand("migrate", paths.invalid), [
|
141
|
+
"All done!",
|
142
|
+
]);
|
143
|
+
|
144
|
+
fs.unlinkSync("./test/migrations-invalid/v3_third.undo.sql");
|
145
|
+
|
146
|
+
assertIncludesAll(
|
147
|
+
await executeCommand("undo", paths.invalid, "--nundos=2"),
|
148
|
+
["Error: not enough sequential (from last) undo migrations to apply"]
|
149
|
+
);
|
150
|
+
});
|
151
|
+
|
152
|
+
it("alter migration", async () => {
|
153
|
+
assertIncludesAll(await executeCommand("migrate", paths.invalid), [
|
154
|
+
"All done!",
|
155
|
+
]);
|
156
|
+
|
157
|
+
fs.writeFileSync(
|
158
|
+
"./test/migrations-invalid/v1_first.sql",
|
159
|
+
"ALTER TABLE test ADD COLUMN test_column TEXT;"
|
160
|
+
);
|
161
|
+
|
162
|
+
assertIncludesAll(await executeCommand("migrate", paths.invalid), [
|
163
|
+
"Versioned migration v1_first.sql has been altered. Cannot migrate in current state.",
|
164
|
+
]);
|
165
|
+
});
|
166
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
ALTER TABLE test ADD COLUMN test_column TEXT;
|
@@ -0,0 +1 @@
|
|
1
|
+
drop table second;
|
@@ -0,0 +1 @@
|
|
1
|
+
drop table third;
|
@@ -0,0 +1 @@
|
|
1
|
+
drop table second;
|
@@ -0,0 +1 @@
|
|
1
|
+
drop table third;
|
@@ -0,0 +1 @@
|
|
1
|
+
drop table second;
|
@@ -0,0 +1 @@
|
|
1
|
+
drop table third;
|
package/test/utils.ts
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
import { exec } from "node:child_process";
|
2
|
+
|
3
|
+
export const execute = (cmd: string) => {
|
4
|
+
const child = exec(cmd);
|
5
|
+
let scriptOutput = "";
|
6
|
+
let scriptError = "";
|
7
|
+
|
8
|
+
if (!child.stdout || !child.stderr) {
|
9
|
+
return { output: "", error: "", exitCode: 1 };
|
10
|
+
}
|
11
|
+
|
12
|
+
child.stdout.setEncoding("utf8");
|
13
|
+
child.stdout.on("data", (data) => {
|
14
|
+
scriptOutput += data.toString();
|
15
|
+
});
|
16
|
+
|
17
|
+
child.stderr.setEncoding("utf8");
|
18
|
+
child.stderr.on("data", (data) => {
|
19
|
+
scriptError += data.toString();
|
20
|
+
});
|
21
|
+
|
22
|
+
return new Promise((res, rej) =>
|
23
|
+
child.on("close", function (code) {
|
24
|
+
res({ output: scriptOutput, error: scriptError, exitCode: code ?? 1 });
|
25
|
+
})
|
26
|
+
) as Promise<{ output: string; error: string; exitCode: number }>;
|
27
|
+
};
|
28
|
+
|
29
|
+
export const includesAll = (output: string, expected: string[]) =>
|
30
|
+
expected.every((x) => output.includes(x));
|
31
|
+
|
32
|
+
export const excludesAll = (output: string, expected: string[]) =>
|
33
|
+
expected.every((x) => !output.includes(x));
|
34
|
+
|
35
|
+
export const includesExcludesAll = (
|
36
|
+
output: string,
|
37
|
+
includes: string[],
|
38
|
+
excludes: string[]
|
39
|
+
) => includesAll(output, includes) && excludesAll(output, excludes);
|
40
|
+
|
41
|
+
export const assertIncludesAll = (
|
42
|
+
{ output, error }: { output: string; error: string },
|
43
|
+
expected: string[]
|
44
|
+
) => {
|
45
|
+
if (!includesAll(output + error, expected)) {
|
46
|
+
console.log(output);
|
47
|
+
console.error(error);
|
48
|
+
throw new Error(`Expected ${expected} to be in output.`);
|
49
|
+
}
|
50
|
+
};
|
51
|
+
export const assertExcludesAll = (
|
52
|
+
{ output, error }: { output: string; error: string },
|
53
|
+
expected: string[]
|
54
|
+
) => {
|
55
|
+
if (!excludesAll(output + error, expected)) {
|
56
|
+
console.log(output);
|
57
|
+
console.error(error);
|
58
|
+
throw new Error(`Expected ${expected} to be be excluded from output.`);
|
59
|
+
}
|
60
|
+
};
|
61
|
+
|
62
|
+
export const assertIncludesExcludesAll = (
|
63
|
+
{ output, error }: { output: string; error: string },
|
64
|
+
includes: string[],
|
65
|
+
excludes: string[]
|
66
|
+
) => {
|
67
|
+
assertIncludesAll({ output, error }, includes);
|
68
|
+
assertExcludesAll({ output, error }, excludes);
|
69
|
+
};
|
package/tsconfig.json
CHANGED
@@ -26,7 +26,7 @@
|
|
26
26
|
|
27
27
|
/* Modules */
|
28
28
|
"module": "commonjs", /* Specify what module code is generated. */
|
29
|
-
"rootDir": "./
|
29
|
+
"rootDir": "./", /* Specify the root folder within your source files. */
|
30
30
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
31
31
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
32
32
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|