@logto/cli 1.0.0-beta.16 → 1.0.0-beta.18
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/bin/logto.js +2 -0
- package/lib/commands/connector/add.js +8 -10
- package/lib/commands/connector/index.js +9 -14
- package/lib/commands/connector/list.js +10 -16
- package/lib/commands/connector/remove.js +13 -18
- package/lib/commands/connector/utils.d.ts +1 -1
- package/lib/commands/connector/utils.js +100 -86
- package/lib/commands/database/alteration/index.d.ts +1 -2
- package/lib/commands/database/alteration/index.js +36 -98
- package/lib/commands/database/alteration/index.test.js +36 -57
- package/lib/commands/database/alteration/meta-url.d.ts +1 -0
- package/lib/commands/database/alteration/meta-url.js +4 -0
- package/lib/commands/database/alteration/type.d.ts +1 -1
- package/lib/commands/database/alteration/type.js +1 -2
- package/lib/commands/database/alteration/utils.d.ts +4 -0
- package/lib/commands/database/alteration/utils.js +39 -0
- package/lib/commands/database/alteration/version.d.ts +1 -1
- package/lib/commands/database/alteration/version.js +18 -25
- package/lib/commands/database/config.js +44 -49
- package/lib/commands/database/index.js +7 -12
- package/lib/commands/database/seed/index.d.ts +1 -1
- package/lib/commands/database/seed/index.js +59 -55
- package/lib/commands/database/seed/oidc-config.js +15 -18
- package/lib/commands/database/utilities.js +6 -11
- package/lib/commands/install/index.d.ts +1 -1
- package/lib/commands/install/index.js +14 -28
- package/lib/commands/install/utils.d.ts +0 -1
- package/lib/commands/install/utils.js +59 -106
- package/lib/constants.js +1 -4
- package/lib/database.js +33 -43
- package/lib/index.js +18 -23
- package/lib/meta-url.d.ts +1 -0
- package/lib/meta-url.js +4 -0
- package/lib/package-json.d.ts +7 -3
- package/lib/package-json.js +28 -27
- package/lib/queries/logto-config.js +18 -26
- package/lib/queries/logto-config.test.js +29 -30
- package/lib/test-utilities.d.ts +2 -2
- package/lib/test-utilities.js +1 -5
- package/lib/utilities.d.ts +5 -5
- package/lib/utilities.js +49 -64
- package/package.json +27 -23
- package/bin/logto +0 -2
package/bin/logto.js
ADDED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const utilities_1 = require("../../utilities");
|
|
4
|
-
const utils_1 = require("./utils");
|
|
1
|
+
import { log } from '../../utilities.js';
|
|
2
|
+
import { addConnectors, addOfficialConnectors, inquireInstancePath } from './utils.js';
|
|
5
3
|
const add = {
|
|
6
4
|
command: ['add [packages...]', 'a', 'install', 'i'],
|
|
7
5
|
describe: 'Add specific Logto connectors',
|
|
@@ -20,17 +18,17 @@ const add = {
|
|
|
20
18
|
"If it's true, the specified package names will be ignored.",
|
|
21
19
|
}),
|
|
22
20
|
handler: async ({ packages: packageNames, path, official }) => {
|
|
23
|
-
const instancePath = await
|
|
21
|
+
const instancePath = await inquireInstancePath(path);
|
|
24
22
|
if (official) {
|
|
25
|
-
await
|
|
23
|
+
await addOfficialConnectors(instancePath);
|
|
26
24
|
}
|
|
27
25
|
else {
|
|
28
26
|
if (!packageNames?.length) {
|
|
29
|
-
|
|
27
|
+
log.error('No connector name provided');
|
|
30
28
|
}
|
|
31
|
-
await
|
|
29
|
+
await addConnectors(instancePath, packageNames);
|
|
32
30
|
}
|
|
33
|
-
|
|
31
|
+
log.info('Restart your Logto instance to get the changes reflected.');
|
|
34
32
|
},
|
|
35
33
|
};
|
|
36
|
-
|
|
34
|
+
export default add;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const essentials_1 = require("@silverhand/essentials");
|
|
7
|
-
const add_1 = __importDefault(require("./add"));
|
|
8
|
-
const list_1 = __importDefault(require("./list"));
|
|
9
|
-
const remove_1 = __importDefault(require("./remove"));
|
|
1
|
+
import { noop } from '@silverhand/essentials';
|
|
2
|
+
import add from './add.js';
|
|
3
|
+
import list from './list.js';
|
|
4
|
+
import remove from './remove.js';
|
|
10
5
|
const connector = {
|
|
11
6
|
command: ['connector', 'c', 'connectors'],
|
|
12
7
|
describe: 'Command for Logto connectors',
|
|
@@ -16,10 +11,10 @@ const connector = {
|
|
|
16
11
|
type: 'string',
|
|
17
12
|
describe: 'The path to your Logto instance directory',
|
|
18
13
|
})
|
|
19
|
-
.command(
|
|
20
|
-
.command(
|
|
21
|
-
.command(
|
|
14
|
+
.command(add)
|
|
15
|
+
.command(list)
|
|
16
|
+
.command(remove)
|
|
22
17
|
.demandCommand(1),
|
|
23
|
-
handler:
|
|
18
|
+
handler: noop,
|
|
24
19
|
};
|
|
25
|
-
|
|
20
|
+
export default connector;
|
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
-
const utils_1 = require("./utils");
|
|
8
|
-
const logConnectorNames = (type, names) => {
|
|
9
|
-
if (names.length === 0) {
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getConnectorPackagesFrom, isOfficialConnector } from './utils.js';
|
|
3
|
+
const logConnectorNames = (type, packages) => {
|
|
4
|
+
if (packages.length === 0) {
|
|
10
5
|
return;
|
|
11
6
|
}
|
|
12
7
|
console.log();
|
|
13
|
-
console.log(
|
|
14
|
-
console.log(
|
|
8
|
+
console.log(chalk.blue(type));
|
|
9
|
+
console.log(packages.map(({ name }) => ' ' + name).join('\n'));
|
|
15
10
|
};
|
|
16
11
|
const list = {
|
|
17
12
|
command: ['list', 'l'],
|
|
18
13
|
describe: 'List added Logto connectors',
|
|
19
14
|
handler: async ({ path: inputPath }) => {
|
|
20
|
-
const packages = await
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const thirdPartyPackages = packageNames.filter((name) => !(0, utils_1.isOfficialConnector)(name));
|
|
15
|
+
const packages = await getConnectorPackagesFrom(inputPath);
|
|
16
|
+
const officialPackages = packages.filter(({ name }) => isOfficialConnector(name));
|
|
17
|
+
const thirdPartyPackages = packages.filter(({ name }) => !isOfficialConnector(name));
|
|
24
18
|
logConnectorNames('official'.toUpperCase(), officialPackages);
|
|
25
19
|
logConnectorNames('3rd-party'.toUpperCase(), thirdPartyPackages);
|
|
26
20
|
},
|
|
27
21
|
};
|
|
28
|
-
|
|
22
|
+
export default list;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const utilities_1 = require("../../utilities");
|
|
9
|
-
const utils_1 = require("./utils");
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { log } from '../../utilities.js';
|
|
4
|
+
import { getConnectorPackagesFrom } from './utils.js';
|
|
10
5
|
const remove = {
|
|
11
6
|
command: ['remove [packages...]', 'rm', 'delete'],
|
|
12
7
|
describe: 'Remove existing Logto connectors',
|
|
@@ -18,30 +13,30 @@ const remove = {
|
|
|
18
13
|
}),
|
|
19
14
|
handler: async ({ path: inputPath, packages: packageNames }) => {
|
|
20
15
|
if (!packageNames?.length) {
|
|
21
|
-
|
|
16
|
+
log.error('No connector name provided');
|
|
22
17
|
}
|
|
23
|
-
const existingPackages = await
|
|
18
|
+
const existingPackages = await getConnectorPackagesFrom(inputPath);
|
|
24
19
|
const notFoundPackageNames = packageNames.filter((current) => !existingPackages.some(({ name }) => current === name));
|
|
25
20
|
if (notFoundPackageNames.length > 0) {
|
|
26
|
-
|
|
27
|
-
.map((name) =>
|
|
21
|
+
log.error(`Cannot remove ${notFoundPackageNames
|
|
22
|
+
.map((name) => chalk.green(name))
|
|
28
23
|
.join(', ')}: not found in your Logto instance directory`);
|
|
29
24
|
}
|
|
30
25
|
const okSymbol = Symbol('Connector removed');
|
|
31
26
|
const result = await Promise.all(packageNames.map(async (current) => {
|
|
32
27
|
const packageInfo = existingPackages.find(({ name }) => name === current);
|
|
33
28
|
try {
|
|
34
|
-
await
|
|
29
|
+
await fs.rm(packageInfo?.path ?? '', { force: true, recursive: true });
|
|
35
30
|
return okSymbol;
|
|
36
31
|
}
|
|
37
32
|
catch (error) {
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
log.warn(`Error while removing ${chalk.green(packageInfo?.name)}`);
|
|
34
|
+
log.warn(error);
|
|
40
35
|
return error;
|
|
41
36
|
}
|
|
42
37
|
}));
|
|
43
38
|
const errorCount = result.filter((value) => value !== okSymbol).length;
|
|
44
|
-
|
|
39
|
+
log.info(`Removed ${result.length - errorCount} connectors`);
|
|
45
40
|
},
|
|
46
41
|
};
|
|
47
|
-
|
|
42
|
+
export default remove;
|
|
@@ -15,6 +15,6 @@ export declare const npmPackResultGuard: z.ZodArray<z.ZodObject<{
|
|
|
15
15
|
export declare const inquireInstancePath: (initialPath?: string) => Promise<string>;
|
|
16
16
|
export declare const normalizePackageName: (name: string) => string;
|
|
17
17
|
export declare const isOfficialConnector: (packageName: string) => boolean;
|
|
18
|
-
export declare const getConnectorPackagesFrom: (instancePath?: string) => Promise<import("../../utilities").ConnectorPackage[]>;
|
|
18
|
+
export declare const getConnectorPackagesFrom: (instancePath?: string) => Promise<import("../../utilities.js").ConnectorPackage[]>;
|
|
19
19
|
export declare const addConnectors: (instancePath: string, packageNames: string[]) => Promise<void>;
|
|
20
20
|
export declare const addOfficialConnectors: (instancePath: string) => Promise<void>;
|
|
@@ -1,42 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const tar_1 = __importDefault(require("tar"));
|
|
18
|
-
const zod_1 = require("zod");
|
|
19
|
-
const constants_1 = require("../../constants");
|
|
20
|
-
const utilities_1 = require("../../utilities");
|
|
21
|
-
const utils_1 = require("../install/utils");
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import { assert, conditionalString, trySafe } from '@silverhand/essentials';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { got } from 'got';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
import pLimit from 'p-limit';
|
|
11
|
+
import pRetry from 'p-retry';
|
|
12
|
+
import tar from 'tar';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { connectorDirectory } from '../../constants.js';
|
|
15
|
+
import { getConnectorPackagesFromDirectory, isTty, log, oraPromise } from '../../utilities.js';
|
|
16
|
+
import { defaultPath } from '../install/utils.js';
|
|
22
17
|
const coreDirectory = 'packages/core';
|
|
23
|
-
const execPromise =
|
|
24
|
-
|
|
18
|
+
const execPromise = promisify(exec);
|
|
19
|
+
export const npmPackResultGuard = z
|
|
25
20
|
.object({
|
|
26
|
-
name:
|
|
27
|
-
version:
|
|
28
|
-
filename:
|
|
21
|
+
name: z.string(),
|
|
22
|
+
version: z.string(),
|
|
23
|
+
filename: z.string(),
|
|
29
24
|
})
|
|
30
25
|
.array();
|
|
31
|
-
const buildPathErrorMessage = (value) => `The path ${
|
|
26
|
+
const buildPathErrorMessage = (value) => `The path ${chalk.green(value)} does not contain a Logto instance, please try another.`;
|
|
32
27
|
const validatePath = async (value) => {
|
|
33
|
-
const corePackageJsonPath =
|
|
34
|
-
if (!
|
|
28
|
+
const corePackageJsonPath = path.resolve(path.join(value, coreDirectory, 'package.json'));
|
|
29
|
+
if (!existsSync(corePackageJsonPath)) {
|
|
35
30
|
return buildPathErrorMessage(value);
|
|
36
31
|
}
|
|
37
|
-
const packageJson = await
|
|
38
|
-
const packageName = await
|
|
39
|
-
.object({ name:
|
|
32
|
+
const packageJson = await fs.readFile(corePackageJsonPath, { encoding: 'utf8' });
|
|
33
|
+
const packageName = await z
|
|
34
|
+
.object({ name: z.string() })
|
|
40
35
|
.parseAsync(JSON.parse(packageJson))
|
|
41
36
|
.then(({ name }) => name)
|
|
42
37
|
.catch(() => '');
|
|
@@ -45,17 +40,17 @@ const validatePath = async (value) => {
|
|
|
45
40
|
}
|
|
46
41
|
return true;
|
|
47
42
|
};
|
|
48
|
-
const inquireInstancePath = async (initialPath) => {
|
|
43
|
+
export const inquireInstancePath = async (initialPath) => {
|
|
49
44
|
const inquire = async () => {
|
|
50
|
-
if (!
|
|
51
|
-
|
|
45
|
+
if (!isTty()) {
|
|
46
|
+
assert(initialPath, new Error('Path is missing'));
|
|
52
47
|
return initialPath;
|
|
53
48
|
}
|
|
54
|
-
const { instancePath } = await
|
|
49
|
+
const { instancePath } = await inquirer.prompt({
|
|
55
50
|
name: 'instancePath',
|
|
56
51
|
message: 'Where is your Logto instance?',
|
|
57
52
|
type: 'input',
|
|
58
|
-
default:
|
|
53
|
+
default: defaultPath,
|
|
59
54
|
filter: (value) => value.trim(),
|
|
60
55
|
validate: validatePath,
|
|
61
56
|
}, { instancePath: initialPath });
|
|
@@ -64,86 +59,105 @@ const inquireInstancePath = async (initialPath) => {
|
|
|
64
59
|
const instancePath = await inquire();
|
|
65
60
|
const validated = await validatePath(instancePath);
|
|
66
61
|
if (validated !== true) {
|
|
67
|
-
|
|
62
|
+
log.error(validated);
|
|
68
63
|
}
|
|
69
64
|
return instancePath;
|
|
70
65
|
};
|
|
71
|
-
exports.inquireInstancePath = inquireInstancePath;
|
|
72
66
|
const packagePrefix = 'connector-';
|
|
73
|
-
const normalizePackageName = (name) => name
|
|
67
|
+
export const normalizePackageName = (name) => name
|
|
74
68
|
.split('/')
|
|
75
69
|
// Prepend prefix to the last fragment if needed
|
|
76
70
|
.map((fragment, index, array) => index === array.length - 1 && !fragment.startsWith(packagePrefix) && !fragment.startsWith('@')
|
|
77
71
|
? packagePrefix + fragment
|
|
78
72
|
: fragment)
|
|
79
73
|
.join('/');
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
const getConnectorDirectory = (instancePath) => path.join(instancePath, coreDirectory, connectorDirectory);
|
|
75
|
+
export const isOfficialConnector = (packageName) => packageName.startsWith('@logto/connector-');
|
|
76
|
+
export const getConnectorPackagesFrom = async (instancePath) => {
|
|
77
|
+
const directory = getConnectorDirectory(await inquireInstancePath(instancePath));
|
|
78
|
+
const packages = await trySafe(getConnectorPackagesFromDirectory(directory));
|
|
79
|
+
if (!packages || packages.length === 0) {
|
|
80
|
+
throw new Error('No connector found');
|
|
81
|
+
}
|
|
82
|
+
return packages;
|
|
87
83
|
};
|
|
88
|
-
|
|
89
|
-
const addConnectors = async (instancePath, packageNames) => {
|
|
84
|
+
export const addConnectors = async (instancePath, packageNames) => {
|
|
90
85
|
const cwd = getConnectorDirectory(instancePath);
|
|
91
|
-
if (!
|
|
92
|
-
await
|
|
86
|
+
if (!existsSync(cwd)) {
|
|
87
|
+
await fs.mkdir(cwd, { recursive: true });
|
|
93
88
|
}
|
|
94
|
-
|
|
89
|
+
log.info('Fetch connector metadata');
|
|
90
|
+
const limit = pLimit(10);
|
|
95
91
|
const results = await Promise.all(packageNames
|
|
96
|
-
.map((name) =>
|
|
92
|
+
.map((name) => normalizePackageName(name))
|
|
97
93
|
.map(async (packageName) => {
|
|
98
94
|
const run = async () => {
|
|
99
95
|
const { stdout } = await execPromise(`npm pack ${packageName} --json`, { cwd });
|
|
100
|
-
const result =
|
|
96
|
+
const result = npmPackResultGuard.parse(JSON.parse(stdout));
|
|
101
97
|
if (!result[0]) {
|
|
102
|
-
throw new Error(`Unable to execute ${
|
|
98
|
+
throw new Error(`Unable to execute ${chalk.green('npm pack')} on package ${chalk.green(packageName)}`);
|
|
103
99
|
}
|
|
104
|
-
const { filename, name } = result[0];
|
|
100
|
+
const { filename, name, version } = result[0];
|
|
105
101
|
const escapedFilename = filename.replace(/\//g, '-').replace(/@/g, '');
|
|
106
|
-
const tarPath =
|
|
107
|
-
const packageDirectory =
|
|
108
|
-
await (
|
|
109
|
-
await (
|
|
110
|
-
await
|
|
111
|
-
await
|
|
112
|
-
|
|
102
|
+
const tarPath = path.join(cwd, escapedFilename);
|
|
103
|
+
const packageDirectory = path.join(cwd, name.replace(/\//g, '-'));
|
|
104
|
+
await fs.rm(packageDirectory, { force: true, recursive: true });
|
|
105
|
+
await fs.mkdir(packageDirectory, { recursive: true });
|
|
106
|
+
await tar.extract({ cwd: packageDirectory, file: tarPath, strip: 1 });
|
|
107
|
+
await fs.unlink(tarPath);
|
|
108
|
+
log.succeed(`Added ${chalk.green(name)} v${version}`);
|
|
113
109
|
};
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
return limit(async () => {
|
|
111
|
+
try {
|
|
112
|
+
await pRetry(run, { retries: 2 });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.warn(`[${packageName}]`, error);
|
|
116
|
+
return packageName;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
121
119
|
}));
|
|
122
120
|
const errorPackages = results.filter(Boolean);
|
|
123
121
|
const errorCount = errorPackages.length;
|
|
124
|
-
|
|
125
|
-
? `Finished with ${errorCount} error${
|
|
122
|
+
log.info(errorCount
|
|
123
|
+
? `Finished with ${errorCount} error${conditionalString(errorCount > 1 && 's')}.`
|
|
126
124
|
: 'Finished');
|
|
127
125
|
if (errorCount) {
|
|
128
|
-
|
|
126
|
+
log.warn('Failed to add ' + errorPackages.map((name) => chalk.green(name)).join(', '));
|
|
129
127
|
}
|
|
130
128
|
};
|
|
131
|
-
exports.addConnectors = addConnectors;
|
|
132
129
|
const officialConnectorPrefix = '@logto/connector-';
|
|
133
130
|
const fetchOfficialConnectorList = async () => {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
const fetchList = async (from = 0, size = 20) => {
|
|
132
|
+
const parameters = new URLSearchParams({
|
|
133
|
+
text: officialConnectorPrefix,
|
|
134
|
+
from: String(from),
|
|
135
|
+
size: String(size),
|
|
136
|
+
});
|
|
137
|
+
return got(`https://registry.npmjs.org/-/v1/search?${parameters.toString()}`).json();
|
|
138
|
+
};
|
|
139
|
+
const packages = [];
|
|
140
|
+
// Disable lint rules for business need
|
|
141
|
+
// eslint-disable-next-line @silverhand/fp/no-let, @silverhand/fp/no-mutation
|
|
142
|
+
for (let page = 0;; ++page) {
|
|
143
|
+
// eslint-disable-next-line no-await-in-loop
|
|
144
|
+
const { objects } = await fetchList(page * 20, 20);
|
|
145
|
+
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
|
146
|
+
packages.push(...objects
|
|
147
|
+
.filter(({ package: { name, scope } }) => scope === 'logto' &&
|
|
148
|
+
['mock', 'kit'].every((excluded) => !name.slice(officialConnectorPrefix.length).startsWith(excluded)))
|
|
149
|
+
.map(({ package: data }) => data));
|
|
150
|
+
if (objects.length < 20) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return packages;
|
|
141
155
|
};
|
|
142
|
-
const addOfficialConnectors = async (instancePath) => {
|
|
143
|
-
const packages = await
|
|
156
|
+
export const addOfficialConnectors = async (instancePath) => {
|
|
157
|
+
const packages = await oraPromise(fetchOfficialConnectorList(), {
|
|
144
158
|
text: 'Fetch official connector list',
|
|
145
|
-
prefixText:
|
|
159
|
+
prefixText: chalk.blue('[info]'),
|
|
146
160
|
});
|
|
147
|
-
|
|
161
|
+
log.info(`Found ${packages.length} official connectors`);
|
|
162
|
+
await addConnectors(instancePath, packages.map(({ name }) => name));
|
|
148
163
|
};
|
|
149
|
-
exports.addOfficialConnectors = addOfficialConnectors;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { DatabasePool } from 'slonik';
|
|
2
2
|
import type { CommandModule } from 'yargs';
|
|
3
|
-
import type { AlterationFile } from './type';
|
|
4
|
-
export declare const getAlterationFiles: () => Promise<AlterationFile[]>;
|
|
3
|
+
import type { AlterationFile } from './type.js';
|
|
5
4
|
export declare const getLatestAlterationTimestamp: () => Promise<number>;
|
|
6
5
|
export declare const getUndeployedAlterations: (pool: DatabasePool) => Promise<AlterationFile[]>;
|
|
7
6
|
declare const alteration: CommandModule<unknown, {
|
|
@@ -1,122 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.getUndeployedAlterations = exports.getLatestAlterationTimestamp = exports.getAlterationFiles = void 0;
|
|
30
|
-
const path_1 = __importDefault(require("path"));
|
|
31
|
-
const shared_1 = require("@logto/shared");
|
|
32
|
-
const essentials_1 = require("@silverhand/essentials");
|
|
33
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
34
|
-
const fs_extra_1 = require("fs-extra");
|
|
35
|
-
const database_1 = require("../../../database");
|
|
36
|
-
const logto_config_1 = require("../../../queries/logto-config");
|
|
37
|
-
const utilities_1 = require("../../../utilities");
|
|
38
|
-
const version_1 = require("./version");
|
|
39
|
-
const alterationFilenameRegex = /-(\d+)-?.*\.js$/;
|
|
40
|
-
const getTimestampFromFilename = (filename) => {
|
|
41
|
-
const match = alterationFilenameRegex.exec(filename);
|
|
42
|
-
if (!match?.[1]) {
|
|
43
|
-
throw new Error(`Can not get timestamp: ${filename}`);
|
|
44
|
-
}
|
|
45
|
-
return Number(match[1]);
|
|
46
|
-
};
|
|
1
|
+
import { conditionalString } from '@silverhand/essentials';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { createPoolFromConfig } from '../../../database.js';
|
|
4
|
+
import { getCurrentDatabaseAlterationTimestamp, updateDatabaseTimestamp, } from '../../../queries/logto-config.js';
|
|
5
|
+
import { log } from '../../../utilities.js';
|
|
6
|
+
import { getAlterationFiles, getTimestampFromFilename } from './utils.js';
|
|
7
|
+
import { chooseAlterationsByVersion } from './version.js';
|
|
47
8
|
const importAlterationScript = async (filePath) => {
|
|
48
9
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
49
|
-
const module = await
|
|
10
|
+
const module = await import(filePath);
|
|
50
11
|
// eslint-disable-next-line no-restricted-syntax
|
|
51
12
|
return module.default;
|
|
52
13
|
};
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
/**
|
|
56
|
-
* We copy all alteration scripts to the CLI package root directory,
|
|
57
|
-
* since they need a proper context that includes required dependencies (such as slonik) in `node_modules/`.
|
|
58
|
-
* While the original `@logto/schemas` may remove them in production.
|
|
59
|
-
*/
|
|
60
|
-
const packageDirectory = await (0, shared_1.findPackage)(
|
|
61
|
-
// Until we migrate to ESM
|
|
62
|
-
// eslint-disable-next-line unicorn/prefer-module
|
|
63
|
-
__dirname);
|
|
64
|
-
const localAlterationDirectory = path_1.default.resolve(
|
|
65
|
-
// Until we migrate to ESM
|
|
66
|
-
// eslint-disable-next-line unicorn/prefer-module
|
|
67
|
-
packageDirectory ?? __dirname, 'alteration-scripts');
|
|
68
|
-
if (!(0, fs_extra_1.existsSync)(alterationDirectory)) {
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
// We need to copy alteration files to execute in the CLI context to make `slonik` available
|
|
72
|
-
await (0, fs_extra_1.remove)(localAlterationDirectory);
|
|
73
|
-
await (0, fs_extra_1.copy)(alterationDirectory, localAlterationDirectory);
|
|
74
|
-
const directory = await (0, fs_extra_1.readdir)(localAlterationDirectory);
|
|
75
|
-
const files = directory.filter((file) => alterationFilenameRegex.test(file));
|
|
76
|
-
return files
|
|
77
|
-
.slice()
|
|
78
|
-
.sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2))
|
|
79
|
-
.map((filename) => ({ path: path_1.default.join(localAlterationDirectory, filename), filename }));
|
|
80
|
-
};
|
|
81
|
-
exports.getAlterationFiles = getAlterationFiles;
|
|
82
|
-
const getLatestAlterationTimestamp = async () => {
|
|
83
|
-
const files = await (0, exports.getAlterationFiles)();
|
|
14
|
+
export const getLatestAlterationTimestamp = async () => {
|
|
15
|
+
const files = await getAlterationFiles();
|
|
84
16
|
const lastFile = files[files.length - 1];
|
|
85
17
|
if (!lastFile) {
|
|
86
18
|
return 0;
|
|
87
19
|
}
|
|
88
20
|
return getTimestampFromFilename(lastFile.filename);
|
|
89
21
|
};
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const files = await (0, exports.getAlterationFiles)();
|
|
22
|
+
export const getUndeployedAlterations = async (pool) => {
|
|
23
|
+
const databaseTimestamp = await getCurrentDatabaseAlterationTimestamp(pool);
|
|
24
|
+
const files = await getAlterationFiles();
|
|
94
25
|
return files.filter(({ filename }) => getTimestampFromFilename(filename) > databaseTimestamp);
|
|
95
26
|
};
|
|
96
|
-
exports.getUndeployedAlterations = getUndeployedAlterations;
|
|
97
27
|
const deployAlteration = async (pool, { path: filePath, filename }) => {
|
|
98
28
|
const { up } = await importAlterationScript(filePath);
|
|
99
29
|
try {
|
|
100
30
|
await pool.transaction(async (connection) => {
|
|
101
31
|
await up(connection);
|
|
102
|
-
await
|
|
32
|
+
await updateDatabaseTimestamp(connection, getTimestampFromFilename(filename));
|
|
103
33
|
});
|
|
104
34
|
}
|
|
105
35
|
catch (error) {
|
|
106
36
|
console.error(error);
|
|
107
37
|
await pool.end();
|
|
108
|
-
|
|
38
|
+
log.error(`Error ocurred during running alteration ${chalk.blue(filename)}.\n\n` +
|
|
109
39
|
" This alteration didn't change anything since it was in a transaction.\n" +
|
|
110
40
|
' Try to fix the error and deploy again.');
|
|
111
41
|
}
|
|
112
|
-
|
|
42
|
+
log.info(`Run alteration ${filename} succeeded`);
|
|
113
43
|
};
|
|
114
44
|
const alteration = {
|
|
115
45
|
command: ['alteration <action> [target]', 'alt', 'alter'],
|
|
116
46
|
describe: 'Perform database alteration',
|
|
117
47
|
builder: (yargs) => yargs
|
|
118
48
|
.positional('action', {
|
|
119
|
-
describe: 'The action to perform, now it only accepts `deploy`',
|
|
49
|
+
describe: 'The action to perform, now it only accepts `deploy` and `list`',
|
|
120
50
|
type: 'string',
|
|
121
51
|
demandOption: true,
|
|
122
52
|
})
|
|
@@ -125,18 +55,26 @@ const alteration = {
|
|
|
125
55
|
type: 'string',
|
|
126
56
|
}),
|
|
127
57
|
handler: async ({ action, target }) => {
|
|
128
|
-
if (action
|
|
129
|
-
|
|
58
|
+
if (action === 'list') {
|
|
59
|
+
const files = await getAlterationFiles();
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
console.log(file.filename);
|
|
62
|
+
}
|
|
130
63
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
64
|
+
else if (action === 'deploy') {
|
|
65
|
+
const pool = await createPoolFromConfig();
|
|
66
|
+
const alterations = await chooseAlterationsByVersion(await getUndeployedAlterations(pool), target);
|
|
67
|
+
log.info(`Found ${alterations.length} alteration${conditionalString(alterations.length > 1 && 's')} to deploy`);
|
|
68
|
+
// The await inside the loop is intended, alterations should run in order
|
|
69
|
+
for (const alteration of alterations) {
|
|
70
|
+
// eslint-disable-next-line no-await-in-loop
|
|
71
|
+
await deployAlteration(pool, alteration);
|
|
72
|
+
}
|
|
73
|
+
await pool.end();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log.error('Unsupported action');
|
|
138
77
|
}
|
|
139
|
-
await pool.end();
|
|
140
78
|
},
|
|
141
79
|
};
|
|
142
|
-
|
|
80
|
+
export default alteration;
|