@logto/cli 1.0.0-beta.15 → 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/lib/database.js
CHANGED
|
@@ -1,75 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const slonik_interceptor_preset_1 = require("slonik-interceptor-preset");
|
|
12
|
-
const zod_1 = require("zod");
|
|
13
|
-
const utilities_1 = require("./utilities");
|
|
14
|
-
exports.defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
|
|
15
|
-
const getDatabaseUrlFromConfig = async () => (await (0, utilities_1.getCliConfigWithPrompt)({
|
|
16
|
-
key: utilities_1.ConfigKey.DatabaseUrl,
|
|
1
|
+
import { convertToPrimitiveOrSql } from '@logto/shared';
|
|
2
|
+
import { assert } from '@silverhand/essentials';
|
|
3
|
+
import decamelize from 'decamelize';
|
|
4
|
+
import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
|
|
5
|
+
import { createInterceptors } from 'slonik-interceptor-preset';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { ConfigKey, getCliConfigWithPrompt, log } from './utilities.js';
|
|
8
|
+
export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
|
|
9
|
+
export const getDatabaseUrlFromConfig = async () => (await getCliConfigWithPrompt({
|
|
10
|
+
key: ConfigKey.DatabaseUrl,
|
|
17
11
|
readableKey: 'Logto database URL',
|
|
18
|
-
defaultValue:
|
|
12
|
+
defaultValue: defaultDatabaseUrl,
|
|
19
13
|
})) ?? '';
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
interceptors: (0, slonik_interceptor_preset_1.createInterceptors)(),
|
|
14
|
+
export const createPoolFromConfig = async () => {
|
|
15
|
+
const databaseUrl = await getDatabaseUrlFromConfig();
|
|
16
|
+
assert(parseDsn(databaseUrl).databaseName, new Error('Database name is required in URL'));
|
|
17
|
+
return createPool(databaseUrl, {
|
|
18
|
+
interceptors: createInterceptors(),
|
|
26
19
|
});
|
|
27
20
|
};
|
|
28
|
-
exports.createPoolFromConfig = createPoolFromConfig;
|
|
29
21
|
/**
|
|
30
22
|
* Create a database pool with the URL in CLI config; if no URL found, prompt to input.
|
|
31
23
|
* If the given database does not exists, it will try to create a new database by connecting to the maintenance database `postgres`.
|
|
32
24
|
*
|
|
33
25
|
* @returns A new database pool with the database URL in config.
|
|
34
26
|
*/
|
|
35
|
-
const createPoolAndDatabaseIfNeeded = async () => {
|
|
27
|
+
export const createPoolAndDatabaseIfNeeded = async () => {
|
|
36
28
|
try {
|
|
37
|
-
return await
|
|
29
|
+
return await createPoolFromConfig();
|
|
38
30
|
}
|
|
39
31
|
catch (error) {
|
|
40
|
-
const result =
|
|
32
|
+
const result = z.object({ code: z.string() }).safeParse(error);
|
|
41
33
|
// Database does not exist, try to create one
|
|
42
34
|
// https://www.postgresql.org/docs/14/errcodes-appendix.html
|
|
43
35
|
if (!(result.success && result.data.code === '3D000')) {
|
|
44
|
-
|
|
36
|
+
log.error(error);
|
|
45
37
|
}
|
|
46
|
-
const databaseUrl = await
|
|
47
|
-
const dsn =
|
|
38
|
+
const databaseUrl = await getDatabaseUrlFromConfig();
|
|
39
|
+
const dsn = parseDsn(databaseUrl);
|
|
48
40
|
// It's ok to fall back to '?' since:
|
|
49
41
|
// - Database name is required to connect in the previous pool
|
|
50
42
|
// - It will throw error when creating database using '?'
|
|
51
43
|
const databaseName = dsn.databaseName ?? '?';
|
|
52
|
-
const maintenancePool = await
|
|
53
|
-
interceptors:
|
|
44
|
+
const maintenancePool = await createPool(stringifyDsn({ ...dsn, databaseName: 'postgres' }), {
|
|
45
|
+
interceptors: createInterceptors(),
|
|
54
46
|
});
|
|
55
|
-
await maintenancePool.query(
|
|
56
|
-
create database ${
|
|
47
|
+
await maintenancePool.query(sql `
|
|
48
|
+
create database ${sql.identifier([databaseName])}
|
|
57
49
|
with
|
|
58
50
|
encoding = 'UTF8'
|
|
59
51
|
connection_limit = -1;
|
|
60
52
|
`);
|
|
61
53
|
await maintenancePool.end();
|
|
62
|
-
|
|
63
|
-
return
|
|
54
|
+
log.succeed(`Created database ${databaseName}`);
|
|
55
|
+
return createPoolFromConfig();
|
|
64
56
|
}
|
|
65
57
|
};
|
|
66
|
-
|
|
67
|
-
const insertInto = (object, table) => {
|
|
58
|
+
export const insertInto = (object, table) => {
|
|
68
59
|
const keys = Object.keys(object);
|
|
69
|
-
return
|
|
70
|
-
insert into ${
|
|
71
|
-
(${
|
|
72
|
-
values (${
|
|
60
|
+
return sql `
|
|
61
|
+
insert into ${sql.identifier([table])}
|
|
62
|
+
(${sql.join(keys.map((key) => sql.identifier([decamelize(key)])), sql `, `)})
|
|
63
|
+
values (${sql.join(keys.map((key) => convertToPrimitiveOrSql(key, object[key] ?? null)), sql `, `)})
|
|
73
64
|
`;
|
|
74
65
|
};
|
|
75
|
-
exports.insertInto = insertInto;
|
package/lib/index.js
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const database_1 = __importDefault(require("./commands/database"));
|
|
12
|
-
const install_1 = __importDefault(require("./commands/install"));
|
|
13
|
-
const package_json_1 = require("./package-json");
|
|
14
|
-
const utilities_1 = require("./utilities");
|
|
15
|
-
void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import connector from './commands/connector/index.js';
|
|
6
|
+
import database from './commands/database/index.js';
|
|
7
|
+
import install from './commands/install/index.js';
|
|
8
|
+
import { packageJson } from './package-json.js';
|
|
9
|
+
import { cliConfig, ConfigKey } from './utilities.js';
|
|
10
|
+
void yargs(hideBin(process.argv))
|
|
16
11
|
.version(false)
|
|
17
12
|
.option('env', {
|
|
18
13
|
alias: ['e', 'env-file'],
|
|
@@ -32,23 +27,23 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
|
32
27
|
})
|
|
33
28
|
.middleware(({ version }) => {
|
|
34
29
|
if (version) {
|
|
35
|
-
console.log(
|
|
30
|
+
console.log(packageJson.name + ' v' + packageJson.version);
|
|
36
31
|
// eslint-disable-next-line unicorn/no-process-exit
|
|
37
32
|
process.exit(0);
|
|
38
33
|
}
|
|
39
34
|
}, true)
|
|
40
35
|
.middleware(({ env, db: databaseUrl }) => {
|
|
41
|
-
|
|
42
|
-
const initialDatabaseUrl = databaseUrl ?? process.env[
|
|
36
|
+
dotenv.config({ path: env });
|
|
37
|
+
const initialDatabaseUrl = databaseUrl ?? process.env[ConfigKey.DatabaseUrl];
|
|
43
38
|
if (initialDatabaseUrl) {
|
|
44
|
-
|
|
39
|
+
cliConfig.set(ConfigKey.DatabaseUrl, initialDatabaseUrl);
|
|
45
40
|
}
|
|
46
41
|
})
|
|
47
|
-
.command(
|
|
48
|
-
.command(
|
|
49
|
-
.command(
|
|
42
|
+
.command(install)
|
|
43
|
+
.command(database)
|
|
44
|
+
.command(connector)
|
|
50
45
|
.demandCommand(1)
|
|
51
|
-
.showHelpOnFail(false, `Specify ${
|
|
46
|
+
.showHelpOnFail(false, `Specify ${chalk.green('--help')} for available options`)
|
|
52
47
|
.strict()
|
|
53
48
|
.parserConfiguration({
|
|
54
49
|
'dot-notation': false,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const metaUrl: string;
|
package/lib/meta-url.js
ADDED
package/lib/package-json.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare const packageJson: {
|
|
|
5
5
|
author: string;
|
|
6
6
|
homepage: string;
|
|
7
7
|
license: string;
|
|
8
|
+
type: string;
|
|
8
9
|
publishConfig: {
|
|
9
10
|
access: string;
|
|
10
11
|
};
|
|
@@ -21,11 +22,13 @@ export declare const packageJson: {
|
|
|
21
22
|
precommit: string;
|
|
22
23
|
"prepare:package-json": string;
|
|
23
24
|
build: string;
|
|
25
|
+
"build:test": string;
|
|
24
26
|
dev: string;
|
|
25
27
|
start: string;
|
|
26
28
|
"start:dev": string;
|
|
27
29
|
lint: string;
|
|
28
30
|
"lint:report": string;
|
|
31
|
+
"test:only": string;
|
|
29
32
|
test: string;
|
|
30
33
|
"test:ci": string;
|
|
31
34
|
prepack: string;
|
|
@@ -37,18 +40,19 @@ export declare const packageJson: {
|
|
|
37
40
|
url: string;
|
|
38
41
|
};
|
|
39
42
|
dependencies: {
|
|
43
|
+
"@logto/core-kit": string;
|
|
40
44
|
"@logto/schemas": string;
|
|
41
45
|
"@logto/shared": string;
|
|
42
46
|
"@silverhand/essentials": string;
|
|
43
47
|
chalk: string;
|
|
44
48
|
decamelize: string;
|
|
45
49
|
dotenv: string;
|
|
46
|
-
"fs-extra": string;
|
|
47
50
|
got: string;
|
|
48
51
|
hpagent: string;
|
|
49
52
|
inquirer: string;
|
|
50
53
|
nanoid: string;
|
|
51
54
|
ora: string;
|
|
55
|
+
"p-limit": string;
|
|
52
56
|
"p-retry": string;
|
|
53
57
|
roarr: string;
|
|
54
58
|
semver: string;
|
|
@@ -61,13 +65,13 @@ export declare const packageJson: {
|
|
|
61
65
|
};
|
|
62
66
|
devDependencies: {
|
|
63
67
|
"@silverhand/eslint-config": string;
|
|
64
|
-
"@silverhand/jest-config": string;
|
|
65
68
|
"@silverhand/ts-config": string;
|
|
66
69
|
"@types/fs-extra": string;
|
|
67
70
|
"@types/inquirer": string;
|
|
68
71
|
"@types/jest": string;
|
|
69
72
|
"@types/node": string;
|
|
70
73
|
"@types/semver": string;
|
|
74
|
+
"@types/sinon": string;
|
|
71
75
|
"@types/tar": string;
|
|
72
76
|
"@types/yargs": string;
|
|
73
77
|
eslint: string;
|
|
@@ -75,7 +79,7 @@ export declare const packageJson: {
|
|
|
75
79
|
"lint-staged": string;
|
|
76
80
|
prettier: string;
|
|
77
81
|
rimraf: string;
|
|
78
|
-
|
|
82
|
+
sinon: string;
|
|
79
83
|
typescript: string;
|
|
80
84
|
};
|
|
81
85
|
eslintConfig: {
|
package/lib/package-json.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.packageJson = void 0;
|
|
4
|
-
exports.packageJson = {
|
|
1
|
+
export const packageJson = {
|
|
5
2
|
"name": "@logto/cli",
|
|
6
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.18",
|
|
7
4
|
"description": "Logto CLI.",
|
|
8
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
9
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
10
7
|
"license": "MPL-2.0",
|
|
8
|
+
"type": "module",
|
|
11
9
|
"publishConfig": {
|
|
12
10
|
"access": "public"
|
|
13
11
|
},
|
|
14
12
|
"main": "lib/index.js",
|
|
15
13
|
"bin": {
|
|
16
|
-
"logto": "bin/logto"
|
|
14
|
+
"logto": "bin/logto.js"
|
|
17
15
|
},
|
|
18
16
|
"files": [
|
|
19
17
|
"bin",
|
|
@@ -27,13 +25,15 @@ exports.packageJson = {
|
|
|
27
25
|
"precommit": "lint-staged",
|
|
28
26
|
"prepare:package-json": "node -p \"'export const packageJson = ' + JSON.stringify(require('./package.json'), undefined, 2) + ';'\" > src/package-json.ts",
|
|
29
27
|
"build": "rimraf lib && pnpm prepare:package-json && tsc -p tsconfig.build.json",
|
|
28
|
+
"build:test": "rimraf lib/ && pnpm prepare:package-json && tsc -p tsconfig.test.json --sourcemap",
|
|
30
29
|
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
|
|
31
30
|
"start": "node .",
|
|
32
|
-
"start:dev": "
|
|
31
|
+
"start:dev": "pnpm build && node .",
|
|
33
32
|
"lint": "eslint --ext .ts src",
|
|
34
33
|
"lint:report": "pnpm lint --format json --output-file report.json",
|
|
35
|
-
"test": "jest",
|
|
36
|
-
"test:
|
|
34
|
+
"test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
35
|
+
"test": "pnpm build:test && pnpm test:only",
|
|
36
|
+
"test:ci": "pnpm test:only",
|
|
37
37
|
"prepack": "pnpm build"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
@@ -43,46 +43,47 @@ exports.packageJson = {
|
|
|
43
43
|
"url": "https://github.com/logto-io/logto/issues"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@logto/
|
|
47
|
-
"@logto/
|
|
48
|
-
"@
|
|
49
|
-
"
|
|
50
|
-
"
|
|
46
|
+
"@logto/core-kit": "workspace:*",
|
|
47
|
+
"@logto/schemas": "workspace:*",
|
|
48
|
+
"@logto/shared": "workspace:*",
|
|
49
|
+
"@silverhand/essentials": "2.1.0",
|
|
50
|
+
"chalk": "^5.0.0",
|
|
51
|
+
"decamelize": "^6.0.0",
|
|
51
52
|
"dotenv": "^16.0.0",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"hpagent": "^1.0.0",
|
|
53
|
+
"got": "^12.5.3",
|
|
54
|
+
"hpagent": "^1.2.0",
|
|
55
55
|
"inquirer": "^8.2.2",
|
|
56
|
-
"nanoid": "^
|
|
57
|
-
"ora": "^
|
|
58
|
-
"p-
|
|
56
|
+
"nanoid": "^4.0.0",
|
|
57
|
+
"ora": "^6.1.2",
|
|
58
|
+
"p-limit": "^4.0.0",
|
|
59
|
+
"p-retry": "^5.1.2",
|
|
59
60
|
"roarr": "^7.11.0",
|
|
60
|
-
"semver": "^7.3.
|
|
61
|
+
"semver": "^7.3.8",
|
|
61
62
|
"slonik": "^30.0.0",
|
|
62
63
|
"slonik-interceptor-preset": "^1.2.10",
|
|
63
64
|
"slonik-sql-tag-raw": "^1.1.4",
|
|
64
65
|
"tar": "^6.1.11",
|
|
65
66
|
"yargs": "^17.6.0",
|
|
66
|
-
"zod": "^3.
|
|
67
|
+
"zod": "^3.20.2"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
70
|
"@silverhand/eslint-config": "1.3.0",
|
|
70
|
-
"@silverhand/jest-config": "1.2.2",
|
|
71
71
|
"@silverhand/ts-config": "1.2.1",
|
|
72
72
|
"@types/fs-extra": "^9.0.13",
|
|
73
73
|
"@types/inquirer": "^8.2.1",
|
|
74
74
|
"@types/jest": "^29.1.2",
|
|
75
75
|
"@types/node": "^16.0.0",
|
|
76
76
|
"@types/semver": "^7.3.12",
|
|
77
|
+
"@types/sinon": "^10.0.13",
|
|
77
78
|
"@types/tar": "^6.1.2",
|
|
78
79
|
"@types/yargs": "^17.0.13",
|
|
79
80
|
"eslint": "^8.21.0",
|
|
80
|
-
"jest": "^29.1
|
|
81
|
+
"jest": "^29.3.1",
|
|
81
82
|
"lint-staged": "^13.0.0",
|
|
82
|
-
"prettier": "^2.
|
|
83
|
+
"prettier": "^2.8.1",
|
|
83
84
|
"rimraf": "^3.0.2",
|
|
84
|
-
"
|
|
85
|
-
"typescript": "^4.
|
|
85
|
+
"sinon": "^15.0.0",
|
|
86
|
+
"typescript": "^4.9.4"
|
|
86
87
|
},
|
|
87
88
|
"eslintConfig": {
|
|
88
89
|
"extends": "@silverhand",
|
|
@@ -1,35 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const { table, fields } = (0, shared_1.convertToIdentifiers)(schemas_1.LogtoConfigs);
|
|
9
|
-
const doesConfigsTableExist = async (pool) => {
|
|
10
|
-
const { rows } = await pool.query((0, slonik_1.sql) `select to_regclass(${schemas_1.LogtoConfigs.table}) as regclass`);
|
|
1
|
+
import { logtoConfigGuards, LogtoConfigs, AlterationStateKey } from '@logto/schemas';
|
|
2
|
+
import { convertToIdentifiers } from '@logto/shared';
|
|
3
|
+
import { sql } from 'slonik';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
const { table, fields } = convertToIdentifiers(LogtoConfigs);
|
|
6
|
+
export const doesConfigsTableExist = async (pool) => {
|
|
7
|
+
const { rows } = await pool.query(sql `select to_regclass(${LogtoConfigs.table}) as regclass`);
|
|
11
8
|
return Boolean(rows[0]?.regclass);
|
|
12
9
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
where ${fields.key} in (${slonik_1.sql.join(keys, (0, slonik_1.sql) `,`)})
|
|
10
|
+
export const getRowsByKeys = async (pool, keys) => pool.query(sql `
|
|
11
|
+
select ${sql.join([fields.key, fields.value], sql `,`)} from ${table}
|
|
12
|
+
where ${fields.key} in (${sql.join(keys, sql `,`)})
|
|
17
13
|
`);
|
|
18
|
-
|
|
19
|
-
const updateValueByKey = async (pool, key, value) => pool.query((0, slonik_1.sql) `
|
|
14
|
+
export const updateValueByKey = async (pool, key, value) => pool.query(sql `
|
|
20
15
|
insert into ${table} (${fields.key}, ${fields.value})
|
|
21
|
-
values (${key}, ${
|
|
16
|
+
values (${key}, ${sql.jsonb(value)})
|
|
22
17
|
on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value}
|
|
23
18
|
`);
|
|
24
|
-
|
|
25
|
-
const getCurrentDatabaseAlterationTimestamp = async (pool) => {
|
|
19
|
+
export const getCurrentDatabaseAlterationTimestamp = async (pool) => {
|
|
26
20
|
try {
|
|
27
|
-
const result = await pool.maybeOne(
|
|
28
|
-
const parsed =
|
|
21
|
+
const result = await pool.maybeOne(sql `select * from ${table} where ${fields.key}=${AlterationStateKey.AlterationState}`);
|
|
22
|
+
const parsed = logtoConfigGuards[AlterationStateKey.AlterationState].safeParse(result?.value);
|
|
29
23
|
return (parsed.success && parsed.data.timestamp) || 0;
|
|
30
24
|
}
|
|
31
25
|
catch (error) {
|
|
32
|
-
const result =
|
|
26
|
+
const result = z.object({ code: z.string() }).safeParse(error);
|
|
33
27
|
// Relation does not exist, treat as 0
|
|
34
28
|
// https://www.postgresql.org/docs/14/errcodes-appendix.html
|
|
35
29
|
if (result.success && result.data.code === '42P01') {
|
|
@@ -38,12 +32,10 @@ const getCurrentDatabaseAlterationTimestamp = async (pool) => {
|
|
|
38
32
|
throw error;
|
|
39
33
|
}
|
|
40
34
|
};
|
|
41
|
-
|
|
42
|
-
const updateDatabaseTimestamp = async (connection, timestamp) => {
|
|
35
|
+
export const updateDatabaseTimestamp = async (connection, timestamp) => {
|
|
43
36
|
const value = {
|
|
44
37
|
timestamp,
|
|
45
38
|
updatedAt: new Date().toISOString(),
|
|
46
39
|
};
|
|
47
|
-
return
|
|
40
|
+
return updateValueByKey(connection, AlterationStateKey.AlterationState, value);
|
|
48
41
|
};
|
|
49
|
-
exports.updateDatabaseTimestamp = updateDatabaseTimestamp;
|
|
@@ -1,60 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const logto_config_1 = require("./logto-config");
|
|
1
|
+
import { AlterationStateKey, LogtoConfigs } from '@logto/schemas';
|
|
2
|
+
import { convertToIdentifiers } from '@logto/shared';
|
|
3
|
+
import { createMockPool, createMockQueryResult, sql } from 'slonik';
|
|
4
|
+
import { expectSqlAssert } from '../test-utilities.js';
|
|
5
|
+
import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './logto-config.js';
|
|
6
|
+
const { jest } = import.meta;
|
|
8
7
|
const mockQuery = jest.fn();
|
|
9
|
-
const pool =
|
|
8
|
+
const pool = createMockPool({
|
|
10
9
|
query: async (sql, values) => {
|
|
11
10
|
return mockQuery(sql, values);
|
|
12
11
|
},
|
|
13
12
|
});
|
|
14
|
-
const { table, fields } =
|
|
13
|
+
const { table, fields } = convertToIdentifiers(LogtoConfigs);
|
|
15
14
|
const timestamp = 1663923776;
|
|
16
15
|
describe('getCurrentDatabaseAlterationTimestamp()', () => {
|
|
17
16
|
it('returns 0 if query failed (table not found)', async () => {
|
|
18
17
|
mockQuery.mockRejectedValueOnce({ code: '42P01' });
|
|
19
|
-
await expect(
|
|
18
|
+
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0);
|
|
20
19
|
});
|
|
21
20
|
it('returns 0 if the row is not found', async () => {
|
|
22
|
-
const expectSql =
|
|
21
|
+
const expectSql = sql `
|
|
23
22
|
select * from ${table} where ${fields.key}=$1
|
|
24
23
|
`;
|
|
25
24
|
mockQuery.mockImplementationOnce(async (sql, values) => {
|
|
26
|
-
|
|
27
|
-
expect(values).toEqual([
|
|
28
|
-
return
|
|
25
|
+
expectSqlAssert(sql, expectSql.sql);
|
|
26
|
+
expect(values).toEqual([AlterationStateKey.AlterationState]);
|
|
27
|
+
return createMockQueryResult([]);
|
|
29
28
|
});
|
|
30
|
-
await expect(
|
|
29
|
+
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0);
|
|
31
30
|
});
|
|
32
31
|
it('returns 0 if the value is in bad format', async () => {
|
|
33
|
-
const expectSql =
|
|
32
|
+
const expectSql = sql `
|
|
34
33
|
select * from ${table} where ${fields.key}=$1
|
|
35
34
|
`;
|
|
36
35
|
mockQuery.mockImplementationOnce(async (sql, values) => {
|
|
37
|
-
|
|
38
|
-
expect(values).toEqual([
|
|
39
|
-
return
|
|
36
|
+
expectSqlAssert(sql, expectSql.sql);
|
|
37
|
+
expect(values).toEqual([AlterationStateKey.AlterationState]);
|
|
38
|
+
return createMockQueryResult([{ value: 'some_value' }]);
|
|
40
39
|
});
|
|
41
|
-
await expect(
|
|
40
|
+
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toBe(0);
|
|
42
41
|
});
|
|
43
42
|
it('returns the timestamp from database', async () => {
|
|
44
|
-
const expectSql =
|
|
43
|
+
const expectSql = sql `
|
|
45
44
|
select * from ${table} where ${fields.key}=$1
|
|
46
45
|
`;
|
|
47
46
|
mockQuery.mockImplementationOnce(async (sql, values) => {
|
|
48
|
-
|
|
49
|
-
expect(values).toEqual([
|
|
47
|
+
expectSqlAssert(sql, expectSql.sql);
|
|
48
|
+
expect(values).toEqual([AlterationStateKey.AlterationState]);
|
|
50
49
|
// @ts-expect-error createMockQueryResult doesn't support jsonb
|
|
51
|
-
return
|
|
50
|
+
return createMockQueryResult([{ value: { timestamp, updatedAt: 'now' } }]);
|
|
52
51
|
});
|
|
53
|
-
await expect(
|
|
52
|
+
await expect(getCurrentDatabaseAlterationTimestamp(pool)).resolves.toEqual(timestamp);
|
|
54
53
|
});
|
|
55
54
|
});
|
|
56
55
|
describe('updateDatabaseTimestamp()', () => {
|
|
57
|
-
const expectSql =
|
|
56
|
+
const expectSql = sql `
|
|
58
57
|
insert into ${table} (${fields.key}, ${fields.value})
|
|
59
58
|
values ($1, $2::jsonb)
|
|
60
59
|
on conflict (${fields.key}) do update set ${fields.value}=excluded.${fields.value}
|
|
@@ -69,13 +68,13 @@ describe('updateDatabaseTimestamp()', () => {
|
|
|
69
68
|
});
|
|
70
69
|
it('sends upsert sql with timestamp and updatedAt', async () => {
|
|
71
70
|
mockQuery.mockImplementationOnce(async (sql, values) => {
|
|
72
|
-
|
|
71
|
+
expectSqlAssert(sql, expectSql.sql);
|
|
73
72
|
expect(values).toEqual([
|
|
74
|
-
|
|
73
|
+
AlterationStateKey.AlterationState,
|
|
75
74
|
JSON.stringify({ timestamp, updatedAt }),
|
|
76
75
|
]);
|
|
77
|
-
return
|
|
76
|
+
return createMockQueryResult([]);
|
|
78
77
|
});
|
|
79
|
-
await
|
|
78
|
+
await updateDatabaseTimestamp(pool, timestamp);
|
|
80
79
|
});
|
|
81
80
|
});
|
package/lib/test-utilities.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { QueryResult, QueryResultRow } from 'slonik';
|
|
2
|
-
import type { PrimitiveValueExpression } from 'slonik/dist/src/types.
|
|
3
|
-
export
|
|
2
|
+
import type { PrimitiveValueExpression } from 'slonik/dist/src/types.js';
|
|
3
|
+
export type QueryType = (sql: string, values: readonly PrimitiveValueExpression[]) => Promise<QueryResult<QueryResultRow>>;
|
|
4
4
|
/**
|
|
5
5
|
* Slonik Query Mock Utils
|
|
6
6
|
**/
|
package/lib/test-utilities.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Copied from core
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.expectSqlAssert = void 0;
|
|
5
2
|
/**
|
|
6
3
|
* Slonik Query Mock Utils
|
|
7
4
|
**/
|
|
8
|
-
const expectSqlAssert = (sql, expectSql) => {
|
|
5
|
+
export const expectSqlAssert = (sql, expectSql) => {
|
|
9
6
|
expect(sql
|
|
10
7
|
.split('\n')
|
|
11
8
|
.map((row) => row.trim())
|
|
@@ -14,4 +11,3 @@ const expectSqlAssert = (sql, expectSql) => {
|
|
|
14
11
|
.map((row) => row.trim())
|
|
15
12
|
.filter(Boolean));
|
|
16
13
|
};
|
|
17
|
-
exports.expectSqlAssert = expectSqlAssert;
|
package/lib/utilities.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Optional } from '@silverhand/essentials';
|
|
2
|
-
import
|
|
2
|
+
import type { Options } from 'ora';
|
|
3
3
|
export declare const safeExecSync: (command: string) => string | undefined;
|
|
4
|
-
|
|
4
|
+
type Log = Readonly<{
|
|
5
5
|
info: typeof console.log;
|
|
6
6
|
succeed: typeof console.log;
|
|
7
7
|
warn: typeof console.log;
|
|
@@ -10,13 +10,13 @@ declare type Log = Readonly<{
|
|
|
10
10
|
export declare const log: Log;
|
|
11
11
|
export declare const downloadFile: (url: string, destination: string) => Promise<unknown>;
|
|
12
12
|
export declare const getPathInModule: (moduleName: string, relativePath?: string) => string;
|
|
13
|
-
export declare const oraPromise: <T>(promise: PromiseLike<T>, options?:
|
|
13
|
+
export declare const oraPromise: <T>(promise: PromiseLike<T>, options?: Options, exitOnError?: boolean) => Promise<T>;
|
|
14
14
|
export declare const isTty: () => boolean;
|
|
15
15
|
export declare enum ConfigKey {
|
|
16
16
|
DatabaseUrl = "DB_URL"
|
|
17
17
|
}
|
|
18
18
|
export declare const cliConfig: Map<ConfigKey, Optional<string>>;
|
|
19
|
-
export
|
|
19
|
+
export type GetCliConfigWithPrompt = {
|
|
20
20
|
key: ConfigKey;
|
|
21
21
|
readableKey: string;
|
|
22
22
|
comments?: string;
|
|
@@ -32,7 +32,7 @@ export declare const getCliConfigWithPrompt: ({ key, readableKey, comments, defa
|
|
|
32
32
|
* findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
|
|
33
33
|
*/
|
|
34
34
|
export declare function findLastIndex<T>(array: readonly T[], predicate: (value: T, index: number, object: readonly T[]) => boolean): number;
|
|
35
|
-
export
|
|
35
|
+
export type ConnectorPackage = {
|
|
36
36
|
name: string;
|
|
37
37
|
path: string;
|
|
38
38
|
};
|