@logto/cli 1.0.0-rc.1 → 1.0.0-rc.3
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 +6 -0
- package/lib/commands/connector/add.d.ts +1 -0
- package/lib/commands/connector/add.js +13 -3
- package/lib/commands/connector/remove.js +1 -1
- package/lib/commands/connector/utils.d.ts +2 -2
- package/lib/commands/connector/utils.js +6 -5
- 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/index.js +2 -1
- package/lib/commands/database/seed/cloud.d.ts +10 -0
- package/lib/commands/database/seed/cloud.js +34 -0
- package/lib/commands/database/seed/index.d.ts +2 -4
- package/lib/commands/database/seed/index.js +24 -129
- 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 +4 -0
- package/lib/commands/database/seed/tables.js +109 -0
- package/lib/commands/database/seed/tenant.d.ts +5 -0
- package/lib/commands/database/seed/tenant.js +53 -0
- package/lib/commands/database/system.d.ts +3 -0
- package/lib/commands/database/system.js +63 -0
- package/lib/commands/database/{utilities.js → utils.js} +2 -2
- package/lib/commands/install/index.d.ts +2 -2
- package/lib/commands/install/index.js +9 -8
- package/lib/commands/install/utils.d.ts +1 -1
- package/lib/commands/install/utils.js +3 -3
- package/lib/connector/consts.d.ts +7 -0
- package/lib/connector/consts.js +10 -0
- package/lib/connector/factories.d.ts +2 -0
- package/lib/connector/factories.js +34 -0
- package/lib/connector/index.d.ts +4 -0
- package/lib/connector/index.js +4 -0
- package/lib/connector/loader.d.ts +2 -0
- package/lib/connector/loader.js +48 -0
- package/lib/connector/types.d.ts +12 -0
- package/lib/connector/types.js +1 -0
- package/lib/connector/utils.d.ts +303 -0
- package/lib/connector/utils.js +51 -0
- package/lib/database.js +3 -4
- package/lib/index.js +1 -1
- package/lib/package-json.d.ts +2 -3
- package/lib/package-json.js +10 -14
- 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.d.ts +5 -0
- package/lib/queries/system.js +13 -4
- package/lib/queries/system.test.js +7 -3
- package/package.json +12 -16
- /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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readdir, readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createDefaultAdminConsoleConfig, defaultTenantId, adminTenantId, defaultManagementApi, createAdminDataInAdminTenant, createMeApiInAdminTenant, createDefaultSignInExperience, createAdminTenantSignInExperience, createDefaultAdminConsoleApplication, createCloudApi, createTenantApplicationRole, createTenantMachineToMachineApplication, createAdminTenantApplicationRole, CloudScope, } from '@logto/schemas';
|
|
4
|
+
import { 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, log } from '../../../utils.js';
|
|
11
|
+
import { appendAdminConsoleRedirectUris } from './cloud.js';
|
|
12
|
+
import { seedOidcConfigs } from './oidc-config.js';
|
|
13
|
+
import { assignScopesToRole, createTenant, seedAdminData } from './tenant.js';
|
|
14
|
+
const getExplicitOrder = (query) => {
|
|
15
|
+
const matched = /\/\*\s*init_order\s*=\s*([\d.]+)\s*\*\//.exec(query)?.[1];
|
|
16
|
+
return matched ? Number(matched) : undefined;
|
|
17
|
+
};
|
|
18
|
+
const compareQuery = ([t1, q1], [t2, q2]) => {
|
|
19
|
+
const o1 = getExplicitOrder(q1);
|
|
20
|
+
const o2 = getExplicitOrder(q2);
|
|
21
|
+
if (o1 === undefined && o2 === undefined) {
|
|
22
|
+
return t1.localeCompare(t2);
|
|
23
|
+
}
|
|
24
|
+
if (o1 === undefined) {
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
if (o2 === undefined) {
|
|
28
|
+
return -1;
|
|
29
|
+
}
|
|
30
|
+
return o1 - o2;
|
|
31
|
+
};
|
|
32
|
+
const lifecycleNames = Object.freeze([
|
|
33
|
+
'before_all',
|
|
34
|
+
'after_all',
|
|
35
|
+
'after_each',
|
|
36
|
+
]);
|
|
37
|
+
export const createTables = async (connection) => {
|
|
38
|
+
const tableDirectory = getPathInModule('@logto/schemas', 'tables');
|
|
39
|
+
const directoryFiles = await readdir(tableDirectory);
|
|
40
|
+
const tableFiles = directoryFiles.filter((file) => file.endsWith('.sql'));
|
|
41
|
+
const queries = await Promise.all(tableFiles.map(async (file) => [
|
|
42
|
+
file,
|
|
43
|
+
await readFile(path.join(tableDirectory, file), 'utf8'),
|
|
44
|
+
]));
|
|
45
|
+
const runLifecycleQuery = async (lifecycle, parameters = {}) => {
|
|
46
|
+
const query = queries.find(([file]) => file.slice(1, -4) === lifecycle)?.[1];
|
|
47
|
+
if (query) {
|
|
48
|
+
await connection.query(sql `${raw(
|
|
49
|
+
/* eslint-disable no-template-curly-in-string */
|
|
50
|
+
query
|
|
51
|
+
.replaceAll('${name}', parameters.name ?? '')
|
|
52
|
+
.replaceAll('${database}', parameters.database ?? '')
|
|
53
|
+
/* eslint-enable no-template-curly-in-string */
|
|
54
|
+
)}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const allQueries = [
|
|
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
|
+
const adminAdminData = createAdminDataInAdminTenant(adminTenantId);
|
|
82
|
+
await seedAdminData(connection, adminAdminData);
|
|
83
|
+
await seedAdminData(connection, createMeApiInAdminTenant());
|
|
84
|
+
const [cloudData, ...cloudAdditionalScopes] = createCloudApi();
|
|
85
|
+
const applicationRole = createTenantApplicationRole();
|
|
86
|
+
await seedAdminData(connection, cloudData, ...cloudAdditionalScopes);
|
|
87
|
+
await connection.query(insertInto(applicationRole, 'roles'));
|
|
88
|
+
await assignScopesToRole(connection, adminTenantId, applicationRole.id, ...cloudAdditionalScopes
|
|
89
|
+
.filter(({ name }) => name === CloudScope.SendSms || name === CloudScope.SendEmail)
|
|
90
|
+
.map(({ id }) => id));
|
|
91
|
+
// Add M2M app for default tenant
|
|
92
|
+
const defaultTenantApplication = createTenantMachineToMachineApplication(defaultTenantId);
|
|
93
|
+
await connection.query(insertInto(defaultTenantApplication, 'applications'));
|
|
94
|
+
await connection.query(insertInto(createAdminTenantApplicationRole(defaultTenantApplication.id, applicationRole.id), 'applications_roles'));
|
|
95
|
+
// Assign all cloud API scopes to role `admin:admin`
|
|
96
|
+
await assignScopesToRole(connection, adminTenantId, adminAdminData.role.id, ...cloudAdditionalScopes.map(({ id }) => id));
|
|
97
|
+
await Promise.all([
|
|
98
|
+
connection.query(insertInto(createDefaultAdminConsoleConfig(defaultTenantId), 'logto_configs')),
|
|
99
|
+
connection.query(insertInto(createDefaultSignInExperience(defaultTenantId), 'sign_in_experiences')),
|
|
100
|
+
connection.query(insertInto(createDefaultAdminConsoleConfig(adminTenantId), 'logto_configs')),
|
|
101
|
+
connection.query(insertInto(createAdminTenantSignInExperience(), 'sign_in_experiences')),
|
|
102
|
+
connection.query(insertInto(createDefaultAdminConsoleApplication(), 'applications')),
|
|
103
|
+
updateDatabaseTimestamp(connection, latestTimestamp),
|
|
104
|
+
]);
|
|
105
|
+
log.succeed('Seed data');
|
|
106
|
+
};
|
|
107
|
+
export const seedCloud = async (connection) => {
|
|
108
|
+
await appendAdminConsoleRedirectUris(connection);
|
|
109
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AdminData, UpdateAdminData, CreateScope } 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 | UpdateAdminData, ...additionalScopes: CreateScope[]) => Promise<void>;
|
|
5
|
+
export declare const assignScopesToRole: (pool: CommonQueryMethods, tenantId: string, roleId: string, ...scopeIds: string[]) => Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
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, ...additionalScopes) => {
|
|
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
|
+
const processRole = async () => {
|
|
23
|
+
if ('id' in role) {
|
|
24
|
+
await pool.query(insertInto(role, 'roles'));
|
|
25
|
+
return role.id;
|
|
26
|
+
}
|
|
27
|
+
// Query by role name for existing roles
|
|
28
|
+
const { id } = await pool.one(sql `
|
|
29
|
+
select id from roles
|
|
30
|
+
where name=${role.name}
|
|
31
|
+
and tenant_id=${String(role.tenantId)}
|
|
32
|
+
`);
|
|
33
|
+
return id;
|
|
34
|
+
};
|
|
35
|
+
await pool.query(insertInto(resource, 'resources'));
|
|
36
|
+
await pool.query(insertInto(scope, 'scopes'));
|
|
37
|
+
await Promise.all(additionalScopes.map(async (scope) => pool.query(insertInto(scope, 'scopes'))));
|
|
38
|
+
const roleId = await processRole();
|
|
39
|
+
await pool.query(insertInto({
|
|
40
|
+
id: generateStandardId(),
|
|
41
|
+
roleId,
|
|
42
|
+
scopeId: scope.id,
|
|
43
|
+
tenantId: resource.tenantId,
|
|
44
|
+
}, 'roles_scopes'));
|
|
45
|
+
};
|
|
46
|
+
export const assignScopesToRole = async (pool, tenantId, roleId, ...scopeIds) => {
|
|
47
|
+
await Promise.all(scopeIds.map(async (scopeId) => pool.query(insertInto({
|
|
48
|
+
id: generateStandardId(),
|
|
49
|
+
roleId,
|
|
50
|
+
scopeId,
|
|
51
|
+
tenantId,
|
|
52
|
+
}, 'roles_scopes'))));
|
|
53
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { systemGuards, systemKeys } from '@logto/schemas';
|
|
2
|
+
import { noop } from '@silverhand/essentials';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { createPoolFromConfig } from '../../database.js';
|
|
5
|
+
import { getRowByKey, updateValueByKey } from '../../queries/system.js';
|
|
6
|
+
import { log } from '../../utils.js';
|
|
7
|
+
const validKeysDisplay = chalk.green(systemKeys.join(', '));
|
|
8
|
+
const validateKey = (key) => {
|
|
9
|
+
// Using `.includes()` will result a type error
|
|
10
|
+
// eslint-disable-next-line unicorn/prefer-includes
|
|
11
|
+
if (!systemKeys.some((element) => element === key)) {
|
|
12
|
+
log.error(`Invalid config key ${chalk.red(key)} found, expected one of ${validKeysDisplay}`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const getConfig = {
|
|
16
|
+
command: 'get <key>',
|
|
17
|
+
describe: 'Get system value of the given key in Logto database',
|
|
18
|
+
builder: (yargs) => yargs.positional('key', {
|
|
19
|
+
describe: `The key to get from database system table, one of ${validKeysDisplay}`,
|
|
20
|
+
type: 'string',
|
|
21
|
+
demandOption: true,
|
|
22
|
+
}),
|
|
23
|
+
handler: async ({ key }) => {
|
|
24
|
+
validateKey(key);
|
|
25
|
+
const pool = await createPoolFromConfig();
|
|
26
|
+
const row = await getRowByKey(pool, key);
|
|
27
|
+
await pool.end();
|
|
28
|
+
const value = row?.value;
|
|
29
|
+
console.log(chalk.magenta(key) +
|
|
30
|
+
'=' +
|
|
31
|
+
(value === undefined ? chalk.gray(value) : chalk.green(JSON.stringify(value))));
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const setConfig = {
|
|
35
|
+
command: 'set <key> <value>',
|
|
36
|
+
describe: 'Set config value of the given key in Logto database',
|
|
37
|
+
builder: (yargs) => yargs
|
|
38
|
+
.positional('key', {
|
|
39
|
+
describe: `The key to get from database system table, one of ${validKeysDisplay}`,
|
|
40
|
+
type: 'string',
|
|
41
|
+
demandOption: true,
|
|
42
|
+
})
|
|
43
|
+
.positional('value', {
|
|
44
|
+
describe: 'The value to set, should be a valid JSON string',
|
|
45
|
+
type: 'string',
|
|
46
|
+
demandOption: true,
|
|
47
|
+
}),
|
|
48
|
+
handler: async ({ key, value }) => {
|
|
49
|
+
validateKey(key);
|
|
50
|
+
const guarded = systemGuards[key].parse(JSON.parse(value));
|
|
51
|
+
const pool = await createPoolFromConfig();
|
|
52
|
+
await updateValueByKey(pool, key, guarded);
|
|
53
|
+
await pool.end();
|
|
54
|
+
log.info(`Update ${chalk.green(key)} succeeded`);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const system = {
|
|
58
|
+
command: ['system'],
|
|
59
|
+
describe: 'Commands for Logto system config',
|
|
60
|
+
builder: (yargs) => yargs.command(getConfig).command(setConfig).demandCommand(1),
|
|
61
|
+
handler: noop,
|
|
62
|
+
};
|
|
63
|
+
export default system;
|
|
@@ -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();
|
|
@@ -2,13 +2,13 @@ import type { CommandModule } from 'yargs';
|
|
|
2
2
|
export type InstallArgs = {
|
|
3
3
|
path?: string;
|
|
4
4
|
skipSeed: boolean;
|
|
5
|
-
|
|
5
|
+
cloud: boolean;
|
|
6
6
|
downloadUrl?: string;
|
|
7
7
|
};
|
|
8
8
|
declare const install: CommandModule<unknown, {
|
|
9
9
|
p?: string;
|
|
10
10
|
ss: boolean;
|
|
11
|
-
|
|
11
|
+
cloud: boolean;
|
|
12
12
|
du?: string;
|
|
13
13
|
}>;
|
|
14
14
|
export default install;
|
|
@@ -1,8 +1,8 @@
|
|
|
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
|
-
const installLogto = async ({ path, skipSeed,
|
|
5
|
+
const installLogto = async ({ path, skipSeed, downloadUrl, cloud }) => {
|
|
6
6
|
validateNodeVersion();
|
|
7
7
|
// Get instance path
|
|
8
8
|
const instancePath = await inquireInstancePath(path);
|
|
@@ -16,7 +16,7 @@ const installLogto = async ({ path, skipSeed, officialConnectors, downloadUrl })
|
|
|
16
16
|
log.info(`Skipped database seeding.\n\n' + ' You can use the ${chalk.green('db seed')} command to seed database when ready.\n`);
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
19
|
-
await seedDatabase(instancePath);
|
|
19
|
+
await seedDatabase(instancePath, cloud);
|
|
20
20
|
}
|
|
21
21
|
// Save to dot env
|
|
22
22
|
await createEnv(instancePath, await getDatabaseUrlFromConfig());
|
|
@@ -38,10 +38,11 @@ const install = {
|
|
|
38
38
|
type: 'boolean',
|
|
39
39
|
default: false,
|
|
40
40
|
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
describe: 'Add official connectors after downloading Logto',
|
|
41
|
+
cloud: {
|
|
42
|
+
describe: 'Init Logto for cloud',
|
|
44
43
|
type: 'boolean',
|
|
44
|
+
hidden: true,
|
|
45
|
+
default: false,
|
|
45
46
|
},
|
|
46
47
|
du: {
|
|
47
48
|
alias: 'download-url',
|
|
@@ -50,8 +51,8 @@ const install = {
|
|
|
50
51
|
hidden: true,
|
|
51
52
|
},
|
|
52
53
|
}),
|
|
53
|
-
handler: async ({ p, ss,
|
|
54
|
-
await installLogto({ path: p, skipSeed: ss,
|
|
54
|
+
handler: async ({ p, ss, cloud, du }) => {
|
|
55
|
+
await installLogto({ path: p, skipSeed: ss, cloud, downloadUrl: du });
|
|
55
56
|
},
|
|
56
57
|
};
|
|
57
58
|
export default install;
|
|
@@ -4,7 +4,7 @@ export declare const inquireInstancePath: (initialPath?: string) => Promise<stri
|
|
|
4
4
|
export declare const validateDatabase: () => Promise<void>;
|
|
5
5
|
export declare const downloadRelease: (url?: string) => Promise<string>;
|
|
6
6
|
export declare const decompress: (toPath: string, tarPath: string) => Promise<void>;
|
|
7
|
-
export declare const seedDatabase: (instancePath: string) => Promise<void>;
|
|
7
|
+
export declare const seedDatabase: (instancePath: string, cloud: boolean) => Promise<void>;
|
|
8
8
|
export declare const createEnv: (instancePath: string, databaseUrl: string) => Promise<void>;
|
|
9
9
|
export declare const logFinale: (instancePath: string) => void;
|
|
10
10
|
export declare const isUrl: (string: string) => boolean;
|
|
@@ -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');
|
|
@@ -92,10 +92,10 @@ export const decompress = async (toPath, tarPath) => {
|
|
|
92
92
|
prefixText: chalk.blue('[info]'),
|
|
93
93
|
}, true);
|
|
94
94
|
};
|
|
95
|
-
export const seedDatabase = async (instancePath) => {
|
|
95
|
+
export const seedDatabase = async (instancePath, cloud) => {
|
|
96
96
|
try {
|
|
97
97
|
const pool = await createPoolAndDatabaseIfNeeded();
|
|
98
|
-
await seedByPool(pool
|
|
98
|
+
await seedByPool(pool);
|
|
99
99
|
await pool.end();
|
|
100
100
|
}
|
|
101
101
|
catch (error) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
|
2
|
+
export const notImplemented = () => {
|
|
3
|
+
throw new ConnectorError(ConnectorErrorCodes.NotImplemented);
|
|
4
|
+
};
|
|
5
|
+
export const defaultConnectorMethods = {
|
|
6
|
+
getAuthorizationUri: notImplemented,
|
|
7
|
+
getUserInfo: notImplemented,
|
|
8
|
+
sendMessage: notImplemented,
|
|
9
|
+
validateConfig: notImplemented,
|
|
10
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { notImplemented } from './consts.js';
|
|
3
|
+
import { loadConnector } from './loader.js';
|
|
4
|
+
import { parseMetadata, validateConnectorModule } from './utils.js';
|
|
5
|
+
// eslint-disable-next-line @silverhand/fp/no-let
|
|
6
|
+
let cachedConnectorFactories;
|
|
7
|
+
export const loadConnectorFactories = async (connectorPackages, ignoreVersionMismatch) => {
|
|
8
|
+
if (cachedConnectorFactories) {
|
|
9
|
+
return cachedConnectorFactories;
|
|
10
|
+
}
|
|
11
|
+
const connectorFactories = await Promise.all(connectorPackages.map(async ({ path: packagePath, name }) => {
|
|
12
|
+
try {
|
|
13
|
+
const createConnector = await loadConnector(packagePath, ignoreVersionMismatch);
|
|
14
|
+
const rawConnector = await createConnector({ getConfig: notImplemented });
|
|
15
|
+
validateConnectorModule(rawConnector);
|
|
16
|
+
return {
|
|
17
|
+
metadata: await parseMetadata(rawConnector.metadata, packagePath),
|
|
18
|
+
type: rawConnector.type,
|
|
19
|
+
createConnector,
|
|
20
|
+
path: packagePath,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof Error) {
|
|
25
|
+
console.log(`${chalk.red(`[load-connector] skip ${chalk.bold(name)} due to error: ${error.message}`)}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}));
|
|
31
|
+
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
32
|
+
cachedConnectorFactories = connectorFactories.filter((connectorFactory) => connectorFactory !== undefined);
|
|
33
|
+
return cachedConnectorFactories;
|
|
34
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import connectorKitMeta from '@logto/connector-kit/package.json' assert { type: 'json' };
|
|
3
|
+
import { satisfies } from 'semver';
|
|
4
|
+
import { isKeyInObject } from './utils.js';
|
|
5
|
+
const connectorKit = '@logto/connector-kit';
|
|
6
|
+
const { version: currentVersion } = connectorKitMeta;
|
|
7
|
+
const checkConnectorKitVersion = (dependencies, ignoreVersionMismatch) => {
|
|
8
|
+
if (isKeyInObject(dependencies, connectorKit)) {
|
|
9
|
+
const value = dependencies[connectorKit];
|
|
10
|
+
if (typeof value === 'string') {
|
|
11
|
+
if (satisfies(currentVersion, value)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const message = `Connector requires ${connectorKit} to be ${value}, but the version here is ${currentVersion}.`;
|
|
15
|
+
if (ignoreVersionMismatch) {
|
|
16
|
+
console.warn(`[warn] ${message}\n\nThis is highly discouraged in production.`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(message);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Cannot find ${connectorKit} in connector's dependency`);
|
|
23
|
+
};
|
|
24
|
+
export const loadConnector = async (connectorPath, ignoreVersionMismatch) => {
|
|
25
|
+
const { default: { dependencies },
|
|
26
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
27
|
+
} = (await import(path.join(connectorPath, 'package.json'), {
|
|
28
|
+
assert: { type: 'json' },
|
|
29
|
+
}));
|
|
30
|
+
checkConnectorKitVersion(dependencies, ignoreVersionMismatch);
|
|
31
|
+
const loaded = await import(path.join(connectorPath, 'lib/index.js'));
|
|
32
|
+
if (isKeyInObject(loaded, 'default')) {
|
|
33
|
+
// CJS pattern
|
|
34
|
+
if (isKeyInObject(loaded.default, 'default')) {
|
|
35
|
+
if (typeof loaded.default.default === 'function') {
|
|
36
|
+
console.log(`[warn] Load connector ${connectorPath} in CJS mode`);
|
|
37
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
38
|
+
return loaded.default.default;
|
|
39
|
+
}
|
|
40
|
+
// ESM pattern
|
|
41
|
+
}
|
|
42
|
+
else if (typeof loaded.default === 'function') {
|
|
43
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
44
|
+
return loaded.default;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Cannot load connector from ${connectorPath}, the connector package may be broken.`);
|
|
48
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AllConnector, CreateConnector } from '@logto/connector-kit';
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic loaded connector type.
|
|
4
|
+
*/
|
|
5
|
+
export type ConnectorFactory<T extends AllConnector = AllConnector> = Pick<T, 'type' | 'metadata'> & {
|
|
6
|
+
createConnector: CreateConnector<AllConnector>;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
export type ConnectorPackage = {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|