@logto/cli 1.0.0-rc.0 → 1.0.0-rc.2
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/LICENSE.cloud +93 -0
- package/lib/commands/connector/add.js +1 -1
- package/lib/commands/connector/remove.js +1 -1
- package/lib/commands/connector/utils.d.ts +1 -1
- package/lib/commands/connector/utils.js +1 -1
- package/lib/commands/database/alteration/index.js +1 -1
- package/lib/commands/database/alteration/index.test.js +1 -1
- package/lib/commands/database/alteration/utils.js +1 -1
- package/lib/commands/database/alteration/version.js +1 -1
- package/lib/commands/database/config.js +39 -17
- package/lib/commands/database/seed/index.d.ts +1 -1
- package/lib/commands/database/seed/index.js +4 -102
- package/lib/commands/database/seed/oidc-config.d.ts +2 -0
- package/lib/commands/database/seed/oidc-config.js +45 -2
- package/lib/commands/database/seed/tables.d.ts +3 -0
- package/lib/commands/database/seed/tables.js +89 -0
- package/lib/commands/database/seed/tenant.d.ts +4 -0
- package/lib/commands/database/seed/tenant.js +31 -0
- package/lib/commands/database/{utilities.js → utils.js} +2 -2
- package/lib/commands/install/index.js +1 -1
- package/lib/commands/install/utils.js +1 -1
- package/lib/database.js +1 -1
- package/lib/index.js +1 -1
- package/lib/package-json.d.ts +0 -3
- package/lib/package-json.js +6 -12
- package/lib/queries/database.d.ts +2 -0
- package/lib/queries/database.js +7 -0
- package/lib/queries/logto-config.d.ts +2 -2
- package/lib/queries/logto-config.js +6 -5
- package/lib/queries/system.test.js +2 -2
- package/package.json +8 -14
- /package/lib/commands/database/{utilities.d.ts → utils.d.ts} +0 -0
- /package/lib/{test-utilities.d.ts → test-utils.d.ts} +0 -0
- /package/lib/{test-utilities.js → test-utils.js} +0 -0
- /package/lib/{utilities.d.ts → utils.d.ts} +0 -0
- /package/lib/{utilities.js → utils.js} +0 -0
package/LICENSE.cloud
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Elastic License 2.0
|
|
2
|
+
|
|
3
|
+
URL: https://www.elastic.co/licensing/elastic-license
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
12
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
13
|
+
available, and prepare derivative works of the software, in each case subject to
|
|
14
|
+
the limitations and conditions below.
|
|
15
|
+
|
|
16
|
+
## Limitations
|
|
17
|
+
|
|
18
|
+
You may not provide the software to third parties as a hosted or managed
|
|
19
|
+
service, where the service provides users with access to any substantial set of
|
|
20
|
+
the features or functionality of the software.
|
|
21
|
+
|
|
22
|
+
You may not move, change, disable, or circumvent the license key functionality
|
|
23
|
+
in the software, and you may not remove or obscure any functionality in the
|
|
24
|
+
software that is protected by the license key.
|
|
25
|
+
|
|
26
|
+
You may not alter, remove, or obscure any licensing, copyright, or other notices
|
|
27
|
+
of the licensor in the software. Any use of the licensor’s trademarks is subject
|
|
28
|
+
to applicable law.
|
|
29
|
+
|
|
30
|
+
## Patents
|
|
31
|
+
|
|
32
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
33
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
34
|
+
sale, import and have imported the software, in each case subject to the
|
|
35
|
+
limitations and conditions in this license. This license does not cover any
|
|
36
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
37
|
+
the software. If you or your company make any written claim that the software
|
|
38
|
+
infringes or contributes to infringement of any patent, your patent license for
|
|
39
|
+
the software granted under these terms ends immediately. If your company makes
|
|
40
|
+
such a claim, your patent license ends immediately for work on behalf of your
|
|
41
|
+
company.
|
|
42
|
+
|
|
43
|
+
## Notices
|
|
44
|
+
|
|
45
|
+
You must ensure that anyone who gets a copy of any part of the software from you
|
|
46
|
+
also gets a copy of these terms.
|
|
47
|
+
|
|
48
|
+
If you modify the software, you must include in any modified copies of the
|
|
49
|
+
software prominent notices stating that you have modified the software.
|
|
50
|
+
|
|
51
|
+
## No Other Rights
|
|
52
|
+
|
|
53
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
54
|
+
these terms.
|
|
55
|
+
|
|
56
|
+
## Termination
|
|
57
|
+
|
|
58
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
59
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
60
|
+
with a notice of your violation, and you cease all violation of this license no
|
|
61
|
+
later than 30 days after you receive that notice, your licenses will be
|
|
62
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
63
|
+
reinstatement, any additional violation of these terms will cause your licenses
|
|
64
|
+
to terminate automatically and permanently.
|
|
65
|
+
|
|
66
|
+
## No Liability
|
|
67
|
+
|
|
68
|
+
*As far as the law allows, the software comes as is, without any warranty or
|
|
69
|
+
condition, and the licensor will not be liable to you for any damages arising
|
|
70
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
71
|
+
legal claim.*
|
|
72
|
+
|
|
73
|
+
## Definitions
|
|
74
|
+
|
|
75
|
+
The **licensor** is the entity offering these terms, and the **software** is the
|
|
76
|
+
software the licensor makes available under these terms, including any portion
|
|
77
|
+
of it.
|
|
78
|
+
|
|
79
|
+
**you** refers to the individual or entity agreeing to these terms.
|
|
80
|
+
|
|
81
|
+
**your company** is any legal entity, sole proprietorship, or other kind of
|
|
82
|
+
organization that you work for, plus all organizations that have control over,
|
|
83
|
+
are under the control of, or are under common control with that
|
|
84
|
+
organization. **control** means ownership of substantially all the assets of an
|
|
85
|
+
entity, or the power to direct its management and policies by vote, contract, or
|
|
86
|
+
otherwise. Control can be direct or indirect.
|
|
87
|
+
|
|
88
|
+
**your licenses** are all the licenses granted to you for the software under
|
|
89
|
+
these terms.
|
|
90
|
+
|
|
91
|
+
**use** means anything you do with the software requiring one of your licenses.
|
|
92
|
+
|
|
93
|
+
**trademark** means trademarks, service marks, and similar rights.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { log } from '../../
|
|
3
|
+
import { log } from '../../utils.js';
|
|
4
4
|
import { getConnectorPackagesFrom } from './utils.js';
|
|
5
5
|
const remove = {
|
|
6
6
|
command: ['remove [packages...]', 'rm', 'delete'],
|
|
@@ -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("../../
|
|
18
|
+
export declare const getConnectorPackagesFrom: (instancePath?: string) => Promise<import("../../utils.js").ConnectorPackage[]>;
|
|
19
19
|
export declare const addConnectors: (instancePath: string, packageNames: string[]) => Promise<void>;
|
|
20
20
|
export declare const addOfficialConnectors: (instancePath: string) => Promise<void>;
|
|
@@ -12,7 +12,7 @@ import pRetry from 'p-retry';
|
|
|
12
12
|
import tar from 'tar';
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
import { connectorDirectory } from '../../constants.js';
|
|
15
|
-
import { getConnectorPackagesFromDirectory, isTty, log, oraPromise } from '../../
|
|
15
|
+
import { getConnectorPackagesFromDirectory, isTty, log, oraPromise } from '../../utils.js';
|
|
16
16
|
import { defaultPath } from '../install/utils.js';
|
|
17
17
|
const coreDirectory = 'packages/core';
|
|
18
18
|
const execPromise = promisify(exec);
|
|
@@ -2,7 +2,7 @@ import { conditionalString } from '@silverhand/essentials';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { createPoolFromConfig } from '../../../database.js';
|
|
4
4
|
import { getCurrentDatabaseAlterationTimestamp, updateDatabaseTimestamp, } from '../../../queries/system.js';
|
|
5
|
-
import { log } from '../../../
|
|
5
|
+
import { log } from '../../../utils.js';
|
|
6
6
|
import { getAlterationFiles, getTimestampFromFilename } from './utils.js';
|
|
7
7
|
import { chooseAlterationsByVersion, chooseRevertAlterationsByVersion } from './version.js';
|
|
8
8
|
const importAlterationScript = async (filePath) => {
|
|
@@ -25,7 +25,7 @@ describe('getAvailableAlterations()', () => {
|
|
|
25
25
|
await expect(getAvailableAlterations(pool)).resolves.toEqual(files);
|
|
26
26
|
});
|
|
27
27
|
it('returns files whose timestamp is greater then database timestamp', async () => {
|
|
28
|
-
getCurrentDatabaseAlterationTimestamp.mockResolvedValue(
|
|
28
|
+
getCurrentDatabaseAlterationTimestamp.mockResolvedValue(1_663_923_770);
|
|
29
29
|
await expect(getAvailableAlterations(pool)).resolves.toEqual([files[1], files[2]]);
|
|
30
30
|
});
|
|
31
31
|
});
|
|
@@ -3,7 +3,7 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { findPackage } from '@logto/shared';
|
|
6
|
-
import { getPathInModule } from '../../../
|
|
6
|
+
import { getPathInModule } from '../../../utils.js';
|
|
7
7
|
const currentDirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const alterationFilenameRegex = /-([\d.]+)-?.*\.js$/;
|
|
9
9
|
export const getTimestampFromFilename = (filename) => {
|
|
@@ -2,7 +2,7 @@ import { assert, conditional } from '@silverhand/essentials';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { SemVer, compare, eq, gt } from 'semver';
|
|
5
|
-
import { findLastIndex, isTty, log } from '../../../
|
|
5
|
+
import { findLastIndex, isTty, log } from '../../../utils.js';
|
|
6
6
|
const getVersionStringFromFilename = (filename) => filename.split('-')[0]?.replaceAll('_', '-') ?? 'unknown';
|
|
7
7
|
const getVersionFromFilename = (filename) => {
|
|
8
8
|
try {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { LogtoOidcConfigKey, logtoConfigGuards, logtoConfigKeys } from '@logto/schemas';
|
|
1
|
+
import { defaultTenantId, LogtoOidcConfigKey, logtoConfigGuards, logtoConfigKeys, } from '@logto/schemas';
|
|
2
2
|
import { deduplicate, noop } from '@silverhand/essentials';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { createPoolFromConfig } from '../../database.js';
|
|
5
5
|
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config.js';
|
|
6
|
-
import { log } from '../../
|
|
7
|
-
import { generateOidcCookieKey, generateOidcPrivateKey } from './
|
|
6
|
+
import { log } from '../../utils.js';
|
|
7
|
+
import { generateOidcCookieKey, generateOidcPrivateKey } from './utils.js';
|
|
8
8
|
const validKeysDisplay = chalk.green(logtoConfigKeys.join(', '));
|
|
9
9
|
const validateKeys = (keys) => {
|
|
10
10
|
const invalidKey = (Array.isArray(keys) ? keys : [keys]).find(
|
|
@@ -40,12 +40,17 @@ const getConfig = {
|
|
|
40
40
|
type: 'string',
|
|
41
41
|
array: true,
|
|
42
42
|
default: [],
|
|
43
|
+
})
|
|
44
|
+
.option('tenantId', {
|
|
45
|
+
describe: 'The tenant to operate',
|
|
46
|
+
type: 'string',
|
|
47
|
+
default: defaultTenantId,
|
|
43
48
|
}),
|
|
44
|
-
handler: async ({ key, keys }) => {
|
|
49
|
+
handler: async ({ key, keys, tenantId }) => {
|
|
45
50
|
const queryKeys = deduplicate([key, ...keys]);
|
|
46
51
|
validateKeys(queryKeys);
|
|
47
52
|
const pool = await createPoolFromConfig();
|
|
48
|
-
const { rows } = await getRowsByKeys(pool, queryKeys);
|
|
53
|
+
const { rows } = await getRowsByKeys(pool, tenantId, queryKeys);
|
|
49
54
|
await pool.end();
|
|
50
55
|
console.log(queryKeys
|
|
51
56
|
.map((currentKey) => {
|
|
@@ -70,12 +75,17 @@ const setConfig = {
|
|
|
70
75
|
describe: 'The value to set, should be a valid JSON string',
|
|
71
76
|
type: 'string',
|
|
72
77
|
demandOption: true,
|
|
78
|
+
})
|
|
79
|
+
.option('tenantId', {
|
|
80
|
+
describe: 'The tenant to operate',
|
|
81
|
+
type: 'string',
|
|
82
|
+
default: defaultTenantId,
|
|
73
83
|
}),
|
|
74
|
-
handler: async ({ key, value }) => {
|
|
84
|
+
handler: async ({ key, value, tenantId }) => {
|
|
75
85
|
validateKeys(key);
|
|
76
86
|
const guarded = logtoConfigGuards[key].parse(JSON.parse(value));
|
|
77
87
|
const pool = await createPoolFromConfig();
|
|
78
|
-
await updateValueByKey(pool, key, guarded);
|
|
88
|
+
await updateValueByKey(pool, tenantId, key, guarded);
|
|
79
89
|
await pool.end();
|
|
80
90
|
log.info(`Update ${chalk.green(key)} succeeded`);
|
|
81
91
|
},
|
|
@@ -83,15 +93,21 @@ const setConfig = {
|
|
|
83
93
|
const rotateConfig = {
|
|
84
94
|
command: 'rotate <key>',
|
|
85
95
|
describe: 'Generate a new private or secret key for the given config key and prepend to the key array',
|
|
86
|
-
builder: (yargs) => yargs
|
|
96
|
+
builder: (yargs) => yargs
|
|
97
|
+
.positional('key', {
|
|
87
98
|
describe: `The key to rotate, one of ${chalk.green(validRotateKeys.join(', '))}`,
|
|
88
99
|
type: 'string',
|
|
89
100
|
demandOption: true,
|
|
101
|
+
})
|
|
102
|
+
.option('tenantId', {
|
|
103
|
+
describe: 'The tenant to operate',
|
|
104
|
+
type: 'string',
|
|
105
|
+
default: defaultTenantId,
|
|
90
106
|
}),
|
|
91
|
-
handler: async ({ key }) => {
|
|
107
|
+
handler: async ({ key, tenantId }) => {
|
|
92
108
|
validateRotateKey(key);
|
|
93
109
|
const pool = await createPoolFromConfig();
|
|
94
|
-
const { rows } = await getRowsByKeys(pool, [key]);
|
|
110
|
+
const { rows } = await getRowsByKeys(pool, tenantId, [key]);
|
|
95
111
|
if (!rows[0]) {
|
|
96
112
|
log.warn('No key found, create a new one');
|
|
97
113
|
}
|
|
@@ -99,16 +115,17 @@ const rotateConfig = {
|
|
|
99
115
|
const parsed = logtoConfigGuards[key].safeParse(rows[0]?.value);
|
|
100
116
|
const original = parsed.success ? parsed.data : [];
|
|
101
117
|
// No need for default. It's already exhaustive
|
|
102
|
-
// eslint-disable-next-line default-case
|
|
103
118
|
switch (key) {
|
|
104
|
-
case LogtoOidcConfigKey.PrivateKeys:
|
|
119
|
+
case LogtoOidcConfigKey.PrivateKeys: {
|
|
105
120
|
return [await generateOidcPrivateKey(), ...original];
|
|
106
|
-
|
|
121
|
+
}
|
|
122
|
+
case LogtoOidcConfigKey.CookieKeys: {
|
|
107
123
|
return [generateOidcCookieKey(), ...original];
|
|
124
|
+
}
|
|
108
125
|
}
|
|
109
126
|
};
|
|
110
127
|
const rotated = await getValue();
|
|
111
|
-
await updateValueByKey(pool, key, rotated);
|
|
128
|
+
await updateValueByKey(pool, tenantId, key, rotated);
|
|
112
129
|
await pool.end();
|
|
113
130
|
log.info(`Rotate ${chalk.green(key)} succeeded, now it has ${rotated.length} keys`);
|
|
114
131
|
},
|
|
@@ -127,14 +144,19 @@ const trimConfig = {
|
|
|
127
144
|
type: 'number',
|
|
128
145
|
default: 1,
|
|
129
146
|
demandOption: true,
|
|
147
|
+
})
|
|
148
|
+
.option('tenantId', {
|
|
149
|
+
describe: 'The tenant to operate',
|
|
150
|
+
type: 'string',
|
|
151
|
+
default: defaultTenantId,
|
|
130
152
|
}),
|
|
131
|
-
handler: async ({ key, length }) => {
|
|
153
|
+
handler: async ({ key, length, tenantId }) => {
|
|
132
154
|
validateRotateKey(key);
|
|
133
155
|
if (length < 1) {
|
|
134
156
|
log.error('Invalid length provided');
|
|
135
157
|
}
|
|
136
158
|
const pool = await createPoolFromConfig();
|
|
137
|
-
const { rows } = await getRowsByKeys(pool, [key]);
|
|
159
|
+
const { rows } = await getRowsByKeys(pool, tenantId, [key]);
|
|
138
160
|
if (!rows[0]) {
|
|
139
161
|
log.warn('No key found, create a new one');
|
|
140
162
|
}
|
|
@@ -147,7 +169,7 @@ const trimConfig = {
|
|
|
147
169
|
return value.slice(0, -length);
|
|
148
170
|
};
|
|
149
171
|
const trimmed = await getValue();
|
|
150
|
-
await updateValueByKey(pool, key, trimmed);
|
|
172
|
+
await updateValueByKey(pool, tenantId, key, trimmed);
|
|
151
173
|
await pool.end();
|
|
152
174
|
log.info(`Trim ${chalk.green(key)} succeeded, now it has ${trimmed.length} keys`);
|
|
153
175
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DatabasePool } from 'slonik';
|
|
2
2
|
import type { CommandModule } from 'yargs';
|
|
3
3
|
declare const seedChoices: readonly ["all", "oidc"];
|
|
4
|
-
type SeedChoice = typeof seedChoices[number];
|
|
4
|
+
type SeedChoice = (typeof seedChoices)[number];
|
|
5
5
|
export declare const seedByPool: (pool: DatabasePool, type: SeedChoice) => Promise<void>;
|
|
6
6
|
declare const seed: CommandModule<Record<string, unknown>, {
|
|
7
7
|
type: string;
|
|
@@ -1,107 +1,10 @@
|
|
|
1
|
-
import { readdir, readFile } from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { generateStandardId } from '@logto/core-kit';
|
|
4
|
-
import { logtoConfigGuards, LogtoOidcConfigKey, managementResource, defaultSignInExperience, createDefaultAdminConsoleConfig, createDemoAppApplication, defaultRole, managementResourceScope, defaultRoleScopeRelation, defaultTenant, } from '@logto/schemas';
|
|
5
|
-
import { Hooks, Tenants } from '@logto/schemas/models';
|
|
6
1
|
import chalk from 'chalk';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { createPoolAndDatabaseIfNeeded, insertInto } from '../../../database.js';
|
|
11
|
-
import { getRowsByKeys, doesConfigsTableExist, updateValueByKey, } from '../../../queries/logto-config.js';
|
|
12
|
-
import { updateDatabaseTimestamp } from '../../../queries/system.js';
|
|
13
|
-
import { getPathInModule, log, oraPromise } from '../../../utilities.js';
|
|
2
|
+
import { createPoolAndDatabaseIfNeeded } from '../../../database.js';
|
|
3
|
+
import { doesConfigsTableExist } from '../../../queries/logto-config.js';
|
|
4
|
+
import { log, oraPromise } from '../../../utils.js';
|
|
14
5
|
import { getLatestAlterationTimestamp } from '../alteration/index.js';
|
|
15
6
|
import { getAlterationDirectory } from '../alteration/utils.js';
|
|
16
|
-
import {
|
|
17
|
-
const getExplicitOrder = (query) => {
|
|
18
|
-
const matched = /\/\*\s*init_order\s*=\s*([\d.]+)\s*\*\//.exec(query)?.[1];
|
|
19
|
-
return matched ? Number(matched) : undefined;
|
|
20
|
-
};
|
|
21
|
-
const compareQuery = ([t1, q1], [t2, q2]) => {
|
|
22
|
-
const o1 = getExplicitOrder(q1);
|
|
23
|
-
const o2 = getExplicitOrder(q2);
|
|
24
|
-
if (o1 === undefined && o2 === undefined) {
|
|
25
|
-
return t1.localeCompare(t2);
|
|
26
|
-
}
|
|
27
|
-
if (o1 === undefined) {
|
|
28
|
-
return 1;
|
|
29
|
-
}
|
|
30
|
-
if (o2 === undefined) {
|
|
31
|
-
return -1;
|
|
32
|
-
}
|
|
33
|
-
return o1 - o2;
|
|
34
|
-
};
|
|
35
|
-
const createTables = async (connection) => {
|
|
36
|
-
const tableDirectory = getPathInModule('@logto/schemas', 'tables');
|
|
37
|
-
const directoryFiles = await readdir(tableDirectory);
|
|
38
|
-
const tableFiles = directoryFiles.filter((file) => file.endsWith('.sql'));
|
|
39
|
-
const queries = await Promise.all(tableFiles.map(async (file) => [
|
|
40
|
-
file,
|
|
41
|
-
await readFile(path.join(tableDirectory, file), 'utf8'),
|
|
42
|
-
]));
|
|
43
|
-
const allQueries = [
|
|
44
|
-
[Hooks.tableName, Hooks.raw],
|
|
45
|
-
[Tenants.tableName, Tenants.raw],
|
|
46
|
-
...queries,
|
|
47
|
-
];
|
|
48
|
-
const sorted = allQueries.slice().sort(compareQuery);
|
|
49
|
-
for (const [, query] of sorted) {
|
|
50
|
-
// eslint-disable-next-line no-await-in-loop
|
|
51
|
-
await connection.query(sql `${raw(query)}`);
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const seedTables = async (connection, latestTimestamp) => {
|
|
55
|
-
await connection.query(insertInto(defaultTenant, 'tenants'));
|
|
56
|
-
await Promise.all([
|
|
57
|
-
connection.query(insertInto(managementResource, 'resources')),
|
|
58
|
-
connection.query(insertInto(managementResourceScope, 'scopes')),
|
|
59
|
-
connection.query(insertInto(createDefaultAdminConsoleConfig(), 'logto_configs')),
|
|
60
|
-
connection.query(insertInto(defaultSignInExperience, 'sign_in_experiences')),
|
|
61
|
-
connection.query(insertInto(createDemoAppApplication(generateStandardId()), 'applications')),
|
|
62
|
-
connection.query(insertInto(defaultRole, 'roles')),
|
|
63
|
-
connection.query(insertInto(defaultRoleScopeRelation, 'roles_scopes')),
|
|
64
|
-
updateDatabaseTimestamp(connection, latestTimestamp),
|
|
65
|
-
]);
|
|
66
|
-
};
|
|
67
|
-
const seedOidcConfigs = async (pool) => {
|
|
68
|
-
const configGuard = z.object({
|
|
69
|
-
key: z.nativeEnum(LogtoOidcConfigKey),
|
|
70
|
-
value: z.unknown(),
|
|
71
|
-
});
|
|
72
|
-
const { rows } = await getRowsByKeys(pool, Object.values(LogtoOidcConfigKey));
|
|
73
|
-
// Filter out valid keys that hold a valid value
|
|
74
|
-
const result = await Promise.all(rows.map(async (row) => {
|
|
75
|
-
try {
|
|
76
|
-
const { key, value } = await configGuard.parseAsync(row);
|
|
77
|
-
await logtoConfigGuards[key].parseAsync(value);
|
|
78
|
-
return key;
|
|
79
|
-
}
|
|
80
|
-
catch { }
|
|
81
|
-
}));
|
|
82
|
-
const existingKeys = new Set(result.filter(Boolean));
|
|
83
|
-
const validOptions = Object.values(LogtoOidcConfigKey).filter((key) => {
|
|
84
|
-
const included = existingKeys.has(key);
|
|
85
|
-
if (included) {
|
|
86
|
-
log.info(`Key ${chalk.green(key)} exists, skipping`);
|
|
87
|
-
}
|
|
88
|
-
return !included;
|
|
89
|
-
});
|
|
90
|
-
// The awaits in loop is intended since we'd like to log info in sequence
|
|
91
|
-
/* eslint-disable no-await-in-loop */
|
|
92
|
-
for (const key of validOptions) {
|
|
93
|
-
const { value, fromEnv } = await oidcConfigReaders[key]();
|
|
94
|
-
if (fromEnv) {
|
|
95
|
-
log.info(`Read config ${chalk.green(key)} from env`);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
log.info(`Generated config ${chalk.green(key)}`);
|
|
99
|
-
}
|
|
100
|
-
await updateValueByKey(pool, key, value);
|
|
101
|
-
}
|
|
102
|
-
/* eslint-enable no-await-in-loop */
|
|
103
|
-
log.succeed('Seed OIDC config');
|
|
104
|
-
};
|
|
7
|
+
import { createTables, seedTables } from './tables.js';
|
|
105
8
|
const seedChoices = Object.freeze(['all', 'oidc']);
|
|
106
9
|
export const seedByPool = async (pool, type) => {
|
|
107
10
|
await pool.transaction(async (connection) => {
|
|
@@ -121,7 +24,6 @@ export const seedByPool = async (pool, type) => {
|
|
|
121
24
|
prefixText: chalk.blue('[info]'),
|
|
122
25
|
});
|
|
123
26
|
}
|
|
124
|
-
await seedOidcConfigs(connection);
|
|
125
27
|
});
|
|
126
28
|
};
|
|
127
29
|
const seed = {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { LogtoOidcConfigType } from '@logto/schemas';
|
|
2
2
|
import { LogtoOidcConfigKey } from '@logto/schemas';
|
|
3
|
+
import type { DatabaseTransactionConnection } from 'slonik';
|
|
4
|
+
export declare const seedOidcConfigs: (pool: DatabaseTransactionConnection, tenantId: string) => Promise<void>;
|
|
3
5
|
/**
|
|
4
6
|
* Each config reader will do the following things in order:
|
|
5
7
|
* 1. Try to read value from env (mimic the behavior from the original core)
|
|
@@ -1,8 +1,51 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
|
-
import { LogtoOidcConfigKey } from '@logto/schemas';
|
|
2
|
+
import { LogtoOidcConfigKey, logtoConfigGuards } from '@logto/schemas';
|
|
3
3
|
import { getEnvAsStringArray } from '@silverhand/essentials';
|
|
4
|
-
import
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { getRowsByKeys, updateValueByKey } from '../../../queries/logto-config.js';
|
|
7
|
+
import { log } from '../../../utils.js';
|
|
8
|
+
import { generateOidcCookieKey, generateOidcPrivateKey } from '../utils.js';
|
|
5
9
|
const isBase64FormatPrivateKey = (key) => !key.includes('-');
|
|
10
|
+
export const seedOidcConfigs = async (pool, tenantId) => {
|
|
11
|
+
const tenantPrefix = `[${tenantId}]`;
|
|
12
|
+
const configGuard = z.object({
|
|
13
|
+
key: z.nativeEnum(LogtoOidcConfigKey),
|
|
14
|
+
value: z.unknown(),
|
|
15
|
+
});
|
|
16
|
+
const { rows } = await getRowsByKeys(pool, tenantId, Object.values(LogtoOidcConfigKey));
|
|
17
|
+
// Filter out valid keys that hold a valid value
|
|
18
|
+
const result = await Promise.all(rows.map(async (row) => {
|
|
19
|
+
try {
|
|
20
|
+
const { key, value } = await configGuard.parseAsync(row);
|
|
21
|
+
await logtoConfigGuards[key].parseAsync(value);
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
}));
|
|
26
|
+
const existingKeys = new Set(result.filter(Boolean));
|
|
27
|
+
const validOptions = Object.values(LogtoOidcConfigKey).filter((key) => {
|
|
28
|
+
const included = existingKeys.has(key);
|
|
29
|
+
if (included) {
|
|
30
|
+
log.info(tenantPrefix, `Key ${chalk.green(key)} exists, skipping`);
|
|
31
|
+
}
|
|
32
|
+
return !included;
|
|
33
|
+
});
|
|
34
|
+
// The awaits in loop is intended since we'd like to log info in sequence
|
|
35
|
+
/* eslint-disable no-await-in-loop */
|
|
36
|
+
for (const key of validOptions) {
|
|
37
|
+
const { value, fromEnv } = await oidcConfigReaders[key]();
|
|
38
|
+
if (fromEnv) {
|
|
39
|
+
log.info(tenantPrefix, `Read config ${chalk.green(key)} from env`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
log.info(tenantPrefix, `Generated config ${chalk.green(key)}`);
|
|
43
|
+
}
|
|
44
|
+
await updateValueByKey(pool, tenantId, key, value);
|
|
45
|
+
}
|
|
46
|
+
/* eslint-enable no-await-in-loop */
|
|
47
|
+
log.succeed(tenantPrefix, 'Seed OIDC config');
|
|
48
|
+
};
|
|
6
49
|
/**
|
|
7
50
|
* Each config reader will do the following things in order:
|
|
8
51
|
* 1. Try to read value from env (mimic the behavior from the original core)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DatabaseTransactionConnection } from 'slonik';
|
|
2
|
+
export declare const createTables: (connection: DatabaseTransactionConnection) => Promise<void>;
|
|
3
|
+
export declare const seedTables: (connection: DatabaseTransactionConnection, latestTimestamp: number) => Promise<void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readdir, readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createDefaultAdminConsoleConfig, defaultTenantId, adminTenantId, defaultManagementApi, createAdminDataInAdminTenant, createMeApiInAdminTenant, createDefaultSignInExperience, createAdminTenantSignInExperience, createDefaultAdminConsoleApplication, } from '@logto/schemas';
|
|
4
|
+
import { Hooks, Tenants } from '@logto/schemas/models';
|
|
5
|
+
import { sql } from 'slonik';
|
|
6
|
+
import { raw } from 'slonik-sql-tag-raw';
|
|
7
|
+
import { insertInto } from '../../../database.js';
|
|
8
|
+
import { getDatabaseName } from '../../../queries/database.js';
|
|
9
|
+
import { updateDatabaseTimestamp } from '../../../queries/system.js';
|
|
10
|
+
import { getPathInModule } from '../../../utils.js';
|
|
11
|
+
import { seedOidcConfigs } from './oidc-config.js';
|
|
12
|
+
import { createTenant, seedAdminData } from './tenant.js';
|
|
13
|
+
const getExplicitOrder = (query) => {
|
|
14
|
+
const matched = /\/\*\s*init_order\s*=\s*([\d.]+)\s*\*\//.exec(query)?.[1];
|
|
15
|
+
return matched ? Number(matched) : undefined;
|
|
16
|
+
};
|
|
17
|
+
const compareQuery = ([t1, q1], [t2, q2]) => {
|
|
18
|
+
const o1 = getExplicitOrder(q1);
|
|
19
|
+
const o2 = getExplicitOrder(q2);
|
|
20
|
+
if (o1 === undefined && o2 === undefined) {
|
|
21
|
+
return t1.localeCompare(t2);
|
|
22
|
+
}
|
|
23
|
+
if (o1 === undefined) {
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
if (o2 === undefined) {
|
|
27
|
+
return -1;
|
|
28
|
+
}
|
|
29
|
+
return o1 - o2;
|
|
30
|
+
};
|
|
31
|
+
const lifecycleNames = Object.freeze([
|
|
32
|
+
'before_all',
|
|
33
|
+
'after_all',
|
|
34
|
+
'after_each',
|
|
35
|
+
]);
|
|
36
|
+
export const createTables = async (connection) => {
|
|
37
|
+
const tableDirectory = getPathInModule('@logto/schemas', 'tables');
|
|
38
|
+
const directoryFiles = await readdir(tableDirectory);
|
|
39
|
+
const tableFiles = directoryFiles.filter((file) => file.endsWith('.sql'));
|
|
40
|
+
const queries = await Promise.all(tableFiles.map(async (file) => [
|
|
41
|
+
file,
|
|
42
|
+
await readFile(path.join(tableDirectory, file), 'utf8'),
|
|
43
|
+
]));
|
|
44
|
+
const runLifecycleQuery = async (lifecycle, parameters = {}) => {
|
|
45
|
+
const query = queries.find(([file]) => file.slice(1, -4) === lifecycle)?.[1];
|
|
46
|
+
if (query) {
|
|
47
|
+
await connection.query(sql `${raw(
|
|
48
|
+
/* eslint-disable no-template-curly-in-string */
|
|
49
|
+
query
|
|
50
|
+
.replaceAll('${name}', parameters.name ?? '')
|
|
51
|
+
.replaceAll('${database}', parameters.database ?? '')
|
|
52
|
+
/* eslint-enable no-template-curly-in-string */
|
|
53
|
+
)}`);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const allQueries = [
|
|
57
|
+
[Hooks.tableName, Hooks.raw],
|
|
58
|
+
[Tenants.tableName, Tenants.raw],
|
|
59
|
+
...queries.filter(([file]) => !lifecycleNames.includes(file.slice(1, -4))),
|
|
60
|
+
];
|
|
61
|
+
const sorted = allQueries.slice().sort(compareQuery);
|
|
62
|
+
const database = await getDatabaseName(connection, true);
|
|
63
|
+
await runLifecycleQuery('before_all', { database });
|
|
64
|
+
/* eslint-disable no-await-in-loop */
|
|
65
|
+
for (const [file, query] of sorted) {
|
|
66
|
+
await connection.query(sql `${raw(query)}`);
|
|
67
|
+
if (!query.includes('/* no_after_each */')) {
|
|
68
|
+
await runLifecycleQuery('after_each', { name: file.split('.')[0], database });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/* eslint-enable no-await-in-loop */
|
|
72
|
+
await runLifecycleQuery('after_all', { database });
|
|
73
|
+
};
|
|
74
|
+
export const seedTables = async (connection, latestTimestamp) => {
|
|
75
|
+
await createTenant(connection, defaultTenantId);
|
|
76
|
+
await seedOidcConfigs(connection, defaultTenantId);
|
|
77
|
+
await seedAdminData(connection, defaultManagementApi);
|
|
78
|
+
await createTenant(connection, adminTenantId);
|
|
79
|
+
await seedOidcConfigs(connection, adminTenantId);
|
|
80
|
+
await seedAdminData(connection, createAdminDataInAdminTenant(defaultTenantId));
|
|
81
|
+
await seedAdminData(connection, createMeApiInAdminTenant());
|
|
82
|
+
await Promise.all([
|
|
83
|
+
connection.query(insertInto(createDefaultAdminConsoleConfig(defaultTenantId), 'logto_configs')),
|
|
84
|
+
connection.query(insertInto(createDefaultSignInExperience(defaultTenantId), 'sign_in_experiences')),
|
|
85
|
+
connection.query(insertInto(createAdminTenantSignInExperience(), 'sign_in_experiences')),
|
|
86
|
+
connection.query(insertInto(createDefaultAdminConsoleApplication(), 'applications')),
|
|
87
|
+
updateDatabaseTimestamp(connection, latestTimestamp),
|
|
88
|
+
]);
|
|
89
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AdminData } from '@logto/schemas';
|
|
2
|
+
import type { CommonQueryMethods } from 'slonik';
|
|
3
|
+
export declare const createTenant: (pool: CommonQueryMethods, tenantId: string) => Promise<void>;
|
|
4
|
+
export declare const seedAdminData: (pool: CommonQueryMethods, data: AdminData) => Promise<void>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { generateStandardId } from '@logto/core-kit';
|
|
2
|
+
import { createTenantMetadata } from '@logto/shared';
|
|
3
|
+
import { assert } from '@silverhand/essentials';
|
|
4
|
+
import { sql } from 'slonik';
|
|
5
|
+
import { raw } from 'slonik-sql-tag-raw';
|
|
6
|
+
import { insertInto } from '../../../database.js';
|
|
7
|
+
import { getDatabaseName } from '../../../queries/database.js';
|
|
8
|
+
export const createTenant = async (pool, tenantId) => {
|
|
9
|
+
const database = await getDatabaseName(pool, true);
|
|
10
|
+
const { parentRole, role, password } = createTenantMetadata(database, tenantId);
|
|
11
|
+
const tenantModel = { id: tenantId, dbUser: role, dbUserPassword: password };
|
|
12
|
+
await pool.query(insertInto(tenantModel, 'tenants'));
|
|
13
|
+
await pool.query(sql `
|
|
14
|
+
create role ${sql.identifier([role])} with inherit login
|
|
15
|
+
password '${raw(password)}'
|
|
16
|
+
in role ${sql.identifier([parentRole])};
|
|
17
|
+
`);
|
|
18
|
+
};
|
|
19
|
+
export const seedAdminData = async (pool, data) => {
|
|
20
|
+
const { resource, scope, role } = data;
|
|
21
|
+
assert(resource.tenantId === scope.tenantId && scope.tenantId === role.tenantId, new Error('All data should have the same tenant ID'));
|
|
22
|
+
await pool.query(insertInto(resource, 'resources'));
|
|
23
|
+
await pool.query(insertInto(scope, 'scopes'));
|
|
24
|
+
await pool.query(insertInto(role, 'roles'));
|
|
25
|
+
await pool.query(insertInto({
|
|
26
|
+
id: generateStandardId(),
|
|
27
|
+
roleId: role.id,
|
|
28
|
+
scopeId: scope.id,
|
|
29
|
+
tenantId: resource.tenantId,
|
|
30
|
+
}, 'roles_scopes'));
|
|
31
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateKeyPair } from 'crypto';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
-
import {
|
|
3
|
+
import { generateStandardId } from '@logto/core-kit';
|
|
4
4
|
export const generateOidcPrivateKey = async (type = 'ec') => {
|
|
5
5
|
if (type === 'rsa') {
|
|
6
6
|
const { privateKey } = await promisify(generateKeyPair)('rsa', {
|
|
@@ -34,4 +34,4 @@ export const generateOidcPrivateKey = async (type = 'ec') => {
|
|
|
34
34
|
}
|
|
35
35
|
throw new Error(`Unsupported private key ${String(type)}`);
|
|
36
36
|
};
|
|
37
|
-
export const generateOidcCookieKey = () =>
|
|
37
|
+
export const generateOidcCookieKey = () => generateStandardId();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { getDatabaseUrlFromConfig } from '../../database.js';
|
|
3
|
-
import { log } from '../../
|
|
3
|
+
import { log } from '../../utils.js';
|
|
4
4
|
import { validateNodeVersion, inquireInstancePath, validateDatabase, downloadRelease, seedDatabase, createEnv, logFinale, decompress, isUrl, } from './utils.js';
|
|
5
5
|
const installLogto = async ({ path, skipSeed, officialConnectors, downloadUrl }) => {
|
|
6
6
|
validateNodeVersion();
|
|
@@ -10,7 +10,7 @@ import * as semver from 'semver';
|
|
|
10
10
|
import tar from 'tar';
|
|
11
11
|
import { createPoolAndDatabaseIfNeeded } from '../../database.js';
|
|
12
12
|
import { packageJson } from '../../package-json.js';
|
|
13
|
-
import { cliConfig, ConfigKey, downloadFile, isTty, log, oraPromise, safeExecSync, } from '../../
|
|
13
|
+
import { cliConfig, ConfigKey, downloadFile, isTty, log, oraPromise, safeExecSync, } from '../../utils.js';
|
|
14
14
|
import { seedByPool } from '../database/seed/index.js';
|
|
15
15
|
export const defaultPath = path.join(os.homedir(), 'logto');
|
|
16
16
|
const pgRequired = new semver.SemVer('14.0.0');
|
package/lib/database.js
CHANGED
|
@@ -4,7 +4,7 @@ import decamelize from 'decamelize';
|
|
|
4
4
|
import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
|
|
5
5
|
import { createInterceptors } from 'slonik-interceptor-preset';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import { ConfigKey, getCliConfigWithPrompt, log } from './
|
|
7
|
+
import { ConfigKey, getCliConfigWithPrompt, log } from './utils.js';
|
|
8
8
|
export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
|
|
9
9
|
export const getDatabaseUrlFromConfig = async () => (await getCliConfigWithPrompt({
|
|
10
10
|
key: ConfigKey.DatabaseUrl,
|
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import connector from './commands/connector/index.js';
|
|
|
6
6
|
import database from './commands/database/index.js';
|
|
7
7
|
import install from './commands/install/index.js';
|
|
8
8
|
import { packageJson } from './package-json.js';
|
|
9
|
-
import { cliConfig, ConfigKey } from './
|
|
9
|
+
import { cliConfig, ConfigKey } from './utils.js';
|
|
10
10
|
void yargs(hideBin(process.argv))
|
|
11
11
|
.version(false)
|
|
12
12
|
.option('env', {
|
package/lib/package-json.d.ts
CHANGED
package/lib/package-json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const packageJson = {
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -46,7 +46,7 @@ export const packageJson = {
|
|
|
46
46
|
"@logto/core-kit": "workspace:*",
|
|
47
47
|
"@logto/schemas": "workspace:*",
|
|
48
48
|
"@logto/shared": "workspace:*",
|
|
49
|
-
"@silverhand/essentials": "2.
|
|
49
|
+
"@silverhand/essentials": "2.2.0",
|
|
50
50
|
"chalk": "^5.0.0",
|
|
51
51
|
"decamelize": "^6.0.0",
|
|
52
52
|
"dotenv": "^16.0.0",
|
|
@@ -67,8 +67,8 @@ export const packageJson = {
|
|
|
67
67
|
"zod": "^3.20.2"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@silverhand/eslint-config": "
|
|
71
|
-
"@silverhand/ts-config": "
|
|
70
|
+
"@silverhand/eslint-config": "2.0.1",
|
|
71
|
+
"@silverhand/ts-config": "2.0.3",
|
|
72
72
|
"@types/inquirer": "^9.0.0",
|
|
73
73
|
"@types/jest": "^29.1.2",
|
|
74
74
|
"@types/node": "^18.11.18",
|
|
@@ -76,21 +76,15 @@ export const packageJson = {
|
|
|
76
76
|
"@types/sinon": "^10.0.13",
|
|
77
77
|
"@types/tar": "^6.1.2",
|
|
78
78
|
"@types/yargs": "^17.0.13",
|
|
79
|
-
"eslint": "^8.
|
|
79
|
+
"eslint": "^8.34.0",
|
|
80
80
|
"jest": "^29.3.1",
|
|
81
81
|
"lint-staged": "^13.0.0",
|
|
82
|
-
"prettier": "^2.8.
|
|
82
|
+
"prettier": "^2.8.2",
|
|
83
83
|
"sinon": "^15.0.0",
|
|
84
84
|
"typescript": "^4.9.4"
|
|
85
85
|
},
|
|
86
86
|
"eslintConfig": {
|
|
87
87
|
"extends": "@silverhand",
|
|
88
|
-
"rules": {
|
|
89
|
-
"complexity": [
|
|
90
|
-
"error",
|
|
91
|
-
11
|
|
92
|
-
]
|
|
93
|
-
},
|
|
94
88
|
"ignorePatterns": [
|
|
95
89
|
"src/package-json.ts"
|
|
96
90
|
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
export const getDatabaseName = async (pool, normalized = false) => {
|
|
3
|
+
const { currentDatabase } = await pool.one(sql `
|
|
4
|
+
select current_database();
|
|
5
|
+
`);
|
|
6
|
+
return normalized ? currentDatabase.replaceAll('-', '_') : currentDatabase;
|
|
7
|
+
};
|
|
@@ -2,5 +2,5 @@ import type { LogtoConfig, LogtoConfigKey, logtoConfigGuards } from '@logto/sche
|
|
|
2
2
|
import type { CommonQueryMethods } from 'slonik';
|
|
3
3
|
import type { z } from 'zod';
|
|
4
4
|
export declare const doesConfigsTableExist: (pool: CommonQueryMethods) => Promise<boolean>;
|
|
5
|
-
export declare const getRowsByKeys: (pool: CommonQueryMethods, keys: LogtoConfigKey[]) => Promise<import("slonik").QueryResult<LogtoConfig>>;
|
|
6
|
-
export declare const updateValueByKey: <T extends LogtoConfigKey>(pool: CommonQueryMethods, key: T, value: z.TypeOf<import("@logto/schemas").LogtoConfigGuard[T]>) => Promise<import("slonik").QueryResult<import("slonik").QueryResultRow>>;
|
|
5
|
+
export declare const getRowsByKeys: (pool: CommonQueryMethods, tenantId: string, keys: LogtoConfigKey[]) => Promise<import("slonik").QueryResult<LogtoConfig>>;
|
|
6
|
+
export declare const updateValueByKey: <T extends LogtoConfigKey>(pool: CommonQueryMethods, tenantId: string, key: T, value: z.TypeOf<import("@logto/schemas").LogtoConfigGuard[T]>) => Promise<import("slonik").QueryResult<import("slonik").QueryResultRow>>;
|
|
@@ -6,13 +6,14 @@ export const doesConfigsTableExist = async (pool) => {
|
|
|
6
6
|
const { rows } = await pool.query(sql `select to_regclass(${LogtoConfigs.table}) as regclass`);
|
|
7
7
|
return Boolean(rows[0]?.regclass);
|
|
8
8
|
};
|
|
9
|
-
export const getRowsByKeys = async (pool, keys) => pool.query(sql `
|
|
9
|
+
export const getRowsByKeys = async (pool, tenantId, keys) => pool.query(sql `
|
|
10
10
|
select ${sql.join([fields.key, fields.value], sql `,`)} from ${table}
|
|
11
|
-
where ${fields.
|
|
11
|
+
where ${fields.tenantId} = ${tenantId}
|
|
12
|
+
and ${fields.key} in (${sql.join(keys, sql `,`)})
|
|
12
13
|
`);
|
|
13
|
-
export const updateValueByKey = async (pool, key, value) => pool.query(sql `
|
|
14
|
-
insert into ${table} (${fields.key}, ${fields.value})
|
|
15
|
-
values (${key}, ${sql.jsonb(value)})
|
|
14
|
+
export const updateValueByKey = async (pool, tenantId, key, value) => pool.query(sql `
|
|
15
|
+
insert into ${table} (${fields.tenantId}, ${fields.key}, ${fields.value})
|
|
16
|
+
values (${tenantId}, ${key}, ${sql.jsonb(value)})
|
|
16
17
|
on conflict (${fields.tenantId}, ${fields.key})
|
|
17
18
|
do update set ${fields.value}=excluded.${fields.value}
|
|
18
19
|
`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AlterationStateKey, Systems } from '@logto/schemas';
|
|
2
2
|
import { convertToIdentifiers } from '@logto/shared';
|
|
3
3
|
import { createMockPool, createMockQueryResult, sql } from 'slonik';
|
|
4
|
-
import { expectSqlAssert } from '../test-
|
|
4
|
+
import { expectSqlAssert } from '../test-utils.js';
|
|
5
5
|
import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './system.js';
|
|
6
6
|
const { jest } = import.meta;
|
|
7
7
|
const mockQuery = jest.fn();
|
|
@@ -11,7 +11,7 @@ const pool = createMockPool({
|
|
|
11
11
|
},
|
|
12
12
|
});
|
|
13
13
|
const { table, fields } = convertToIdentifiers(Systems);
|
|
14
|
-
const timestamp =
|
|
14
|
+
const timestamp = 1_663_923_776;
|
|
15
15
|
const systemsTableExists = async () => createMockQueryResult([{ regclass: true }]);
|
|
16
16
|
describe('getCurrentDatabaseAlterationTimestamp()', () => {
|
|
17
17
|
it('returns 0 if query failed (table not found)', async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"url": "https://github.com/logto-io/logto/issues"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@silverhand/essentials": "2.
|
|
31
|
+
"@silverhand/essentials": "2.2.0",
|
|
32
32
|
"chalk": "^5.0.0",
|
|
33
33
|
"decamelize": "^6.0.0",
|
|
34
34
|
"dotenv": "^16.0.0",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"yargs": "^17.6.0",
|
|
49
49
|
"zod": "^3.20.2",
|
|
50
50
|
"@logto/core-kit": "1.0.0-rc.0",
|
|
51
|
-
"@logto/schemas": "1.0.0-rc.
|
|
52
|
-
"@logto/shared": "1.0.0-rc.
|
|
51
|
+
"@logto/schemas": "1.0.0-rc.1",
|
|
52
|
+
"@logto/shared": "1.0.0-rc.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@silverhand/eslint-config": "
|
|
56
|
-
"@silverhand/ts-config": "
|
|
55
|
+
"@silverhand/eslint-config": "2.0.1",
|
|
56
|
+
"@silverhand/ts-config": "2.0.3",
|
|
57
57
|
"@types/inquirer": "^9.0.0",
|
|
58
58
|
"@types/jest": "^29.1.2",
|
|
59
59
|
"@types/node": "^18.11.18",
|
|
@@ -61,21 +61,15 @@
|
|
|
61
61
|
"@types/sinon": "^10.0.13",
|
|
62
62
|
"@types/tar": "^6.1.2",
|
|
63
63
|
"@types/yargs": "^17.0.13",
|
|
64
|
-
"eslint": "^8.
|
|
64
|
+
"eslint": "^8.34.0",
|
|
65
65
|
"jest": "^29.3.1",
|
|
66
66
|
"lint-staged": "^13.0.0",
|
|
67
|
-
"prettier": "^2.8.
|
|
67
|
+
"prettier": "^2.8.2",
|
|
68
68
|
"sinon": "^15.0.0",
|
|
69
69
|
"typescript": "^4.9.4"
|
|
70
70
|
},
|
|
71
71
|
"eslintConfig": {
|
|
72
72
|
"extends": "@silverhand",
|
|
73
|
-
"rules": {
|
|
74
|
-
"complexity": [
|
|
75
|
-
"error",
|
|
76
|
-
11
|
|
77
|
-
]
|
|
78
|
-
},
|
|
79
73
|
"ignorePatterns": [
|
|
80
74
|
"src/package-json.ts"
|
|
81
75
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|