@logto/cli 1.0.0-rc.2 → 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.
Files changed (40) hide show
  1. package/LICENSE +6 -0
  2. package/lib/commands/connector/add.d.ts +1 -0
  3. package/lib/commands/connector/add.js +12 -2
  4. package/lib/commands/connector/utils.d.ts +1 -1
  5. package/lib/commands/connector/utils.js +5 -4
  6. package/lib/commands/database/index.js +2 -1
  7. package/lib/commands/database/seed/cloud.d.ts +10 -0
  8. package/lib/commands/database/seed/cloud.js +34 -0
  9. package/lib/commands/database/seed/index.d.ts +2 -4
  10. package/lib/commands/database/seed/index.js +21 -28
  11. package/lib/commands/database/seed/tables.d.ts +1 -0
  12. package/lib/commands/database/seed/tables.js +25 -5
  13. package/lib/commands/database/seed/tenant.d.ts +3 -2
  14. package/lib/commands/database/seed/tenant.js +25 -3
  15. package/lib/commands/database/system.d.ts +3 -0
  16. package/lib/commands/database/system.js +63 -0
  17. package/lib/commands/install/index.d.ts +2 -2
  18. package/lib/commands/install/index.js +8 -7
  19. package/lib/commands/install/utils.d.ts +1 -1
  20. package/lib/commands/install/utils.js +2 -2
  21. package/lib/connector/consts.d.ts +7 -0
  22. package/lib/connector/consts.js +10 -0
  23. package/lib/connector/factories.d.ts +2 -0
  24. package/lib/connector/factories.js +34 -0
  25. package/lib/connector/index.d.ts +4 -0
  26. package/lib/connector/index.js +4 -0
  27. package/lib/connector/loader.d.ts +2 -0
  28. package/lib/connector/loader.js +48 -0
  29. package/lib/connector/types.d.ts +12 -0
  30. package/lib/connector/types.js +1 -0
  31. package/lib/connector/utils.d.ts +303 -0
  32. package/lib/connector/utils.js +51 -0
  33. package/lib/database.js +2 -3
  34. package/lib/package-json.d.ts +2 -0
  35. package/lib/package-json.js +6 -4
  36. package/lib/queries/system.d.ts +5 -0
  37. package/lib/queries/system.js +13 -4
  38. package/lib/queries/system.test.js +5 -1
  39. package/package.json +6 -4
  40. package/LICENSE.cloud +0 -93
package/LICENSE CHANGED
@@ -1,3 +1,9 @@
1
+ Portions of this software are licensed as follows:
2
+
3
+ * All content that resides under the "packages/cloud" directory of this repository, if that directory exists, is licensed under the license defined in "packages/cloud/LICENSE" (Elastic-2.0).
4
+ * All third party components incorporated into this software are licensed under the original license provided by the owner of the applicable component.
5
+ * Content outside of the above mentioned directories or restrictions above is available under the "MPL-2.0" license as defined below.
6
+
1
7
  Mozilla Public License Version 2.0
2
8
  ==================================
3
9
 
@@ -5,5 +5,6 @@ declare const add: CommandModule<{
5
5
  packages?: string[];
6
6
  path?: string;
7
7
  official: boolean;
8
+ cloud: boolean;
8
9
  }>;
9
10
  export default add;
@@ -16,11 +16,21 @@ const add = {
16
16
  default: false,
17
17
  describe: 'Add all official connectors.\n' +
18
18
  "If it's true, the specified package names will be ignored.",
19
+ })
20
+ .option('cloud', {
21
+ describe: 'Add additional connectors for Logto Cloud',
22
+ type: 'boolean',
23
+ default: false,
24
+ hidden: true,
19
25
  }),
20
- handler: async ({ packages: packageNames, path, official }) => {
26
+ handler: async ({ packages: packageNames, path, official, cloud }) => {
21
27
  const instancePath = await inquireInstancePath(path);
28
+ if (cloud && !official) {
29
+ log.error('--cloud option can only be used with --official option');
30
+ }
22
31
  if (official) {
23
- await addOfficialConnectors(instancePath);
32
+ // Add demo connectors for Logto Cloud only
33
+ await addOfficialConnectors(instancePath, cloud);
24
34
  }
25
35
  else {
26
36
  if (!packageNames?.length) {
@@ -17,4 +17,4 @@ export declare const normalizePackageName: (name: string) => string;
17
17
  export declare const isOfficialConnector: (packageName: string) => boolean;
18
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
- export declare const addOfficialConnectors: (instancePath: string) => Promise<void>;
20
+ export declare const addOfficialConnectors: (instancePath: string, includingCloudConnectors?: boolean) => Promise<void>;
@@ -127,7 +127,7 @@ export const addConnectors = async (instancePath, packageNames) => {
127
127
  }
128
128
  };
129
129
  const officialConnectorPrefix = '@logto/connector-';
130
- const fetchOfficialConnectorList = async () => {
130
+ const fetchOfficialConnectorList = async (includingCloudConnectors = false) => {
131
131
  const fetchList = async (from = 0, size = 20) => {
132
132
  const parameters = new URLSearchParams({
133
133
  text: officialConnectorPrefix,
@@ -142,10 +142,11 @@ const fetchOfficialConnectorList = async () => {
142
142
  for (let page = 0;; ++page) {
143
143
  // eslint-disable-next-line no-await-in-loop
144
144
  const { objects } = await fetchList(page * 20, 20);
145
+ const excludeList = ['mock', 'kit', ...(includingCloudConnectors ? [] : ['logto'])];
145
146
  // eslint-disable-next-line @silverhand/fp/no-mutating-methods
146
147
  packages.push(...objects
147
148
  .filter(({ package: { name, scope } }) => scope === 'logto' &&
148
- ['mock', 'kit'].every((excluded) => !name.slice(officialConnectorPrefix.length).startsWith(excluded)))
149
+ excludeList.every((excluded) => !name.slice(officialConnectorPrefix.length).startsWith(excluded)))
149
150
  .map(({ package: data }) => data));
150
151
  if (objects.length < 20) {
151
152
  break;
@@ -153,8 +154,8 @@ const fetchOfficialConnectorList = async () => {
153
154
  }
154
155
  return packages;
155
156
  };
156
- export const addOfficialConnectors = async (instancePath) => {
157
- const packages = await oraPromise(fetchOfficialConnectorList(), {
157
+ export const addOfficialConnectors = async (instancePath, includingCloudConnectors = false) => {
158
+ const packages = await oraPromise(fetchOfficialConnectorList(includingCloudConnectors), {
158
159
  text: 'Fetch official connector list',
159
160
  prefixText: chalk.blue('[info]'),
160
161
  });
@@ -2,10 +2,11 @@ import { noop } from '@silverhand/essentials';
2
2
  import alteration from './alteration/index.js';
3
3
  import config from './config.js';
4
4
  import seed from './seed/index.js';
5
+ import system from './system.js';
5
6
  const database = {
6
7
  command: ['database', 'db'],
7
8
  describe: 'Commands for Logto database',
8
- builder: (yargs) => yargs.command(config).command(seed).command(alteration).demandCommand(1),
9
+ builder: (yargs) => yargs.command(config).command(seed).command(alteration).command(system).demandCommand(1),
9
10
  handler: noop,
10
11
  };
11
12
  export default database;
@@ -0,0 +1,10 @@
1
+ import type { CommonQueryMethods } from 'slonik';
2
+ /**
3
+ * Append Redirect URIs for the default tenant callback in cloud Admin Console.
4
+ * It reads the same env variables as core to construct the cloud `UrlSet`.
5
+ *
6
+ * E.g., by default, it will appends `http://localhost:3003/default/callback` to the Redirect URIs.
7
+ *
8
+ * For why it is necessary, see the redirect lifecycle of cloud Admin Console.
9
+ */
10
+ export declare const appendAdminConsoleRedirectUris: (pool: CommonQueryMethods) => Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { adminConsoleApplicationId, adminTenantId, defaultTenantId } from '@logto/schemas';
2
+ import { GlobalValues } from '@logto/shared';
3
+ import { appendPath } from '@silverhand/essentials';
4
+ import { sql } from 'slonik';
5
+ import { log } from '../../../utils.js';
6
+ /**
7
+ * Append Redirect URIs for the default tenant callback in cloud Admin Console.
8
+ * It reads the same env variables as core to construct the cloud `UrlSet`.
9
+ *
10
+ * E.g., by default, it will appends `http://localhost:3003/default/callback` to the Redirect URIs.
11
+ *
12
+ * For why it is necessary, see the redirect lifecycle of cloud Admin Console.
13
+ */
14
+ export const appendAdminConsoleRedirectUris = async (pool) => {
15
+ const redirectUris = new GlobalValues().cloudUrlSet
16
+ .deduplicated()
17
+ .flatMap((endpoint) => [defaultTenantId, adminTenantId].map((tenantId) => appendPath(endpoint, tenantId, 'callback')));
18
+ const metadataKey = sql.identifier(['oidc_client_metadata']);
19
+ // Copied from packages/cloud/src/queries/tenants.ts
20
+ // Can be merged into the original once we remove slonik
21
+ await pool.query(sql `
22
+ update applications
23
+ set ${metadataKey} = jsonb_set(
24
+ ${metadataKey},
25
+ '{redirectUris}',
26
+ (select jsonb_agg(distinct value) from jsonb_array_elements(
27
+ ${metadataKey}->'redirectUris' || ${sql.jsonb(redirectUris.map(String))}
28
+ ))
29
+ )
30
+ where id = ${adminConsoleApplicationId}
31
+ and tenant_id = ${adminTenantId}
32
+ `);
33
+ log.succeed('Appended initial Redirect URIs to Admin Console:', redirectUris.map(String));
34
+ };
@@ -1,10 +1,8 @@
1
1
  import type { DatabasePool } from 'slonik';
2
2
  import type { CommandModule } from 'yargs';
3
- declare const seedChoices: readonly ["all", "oidc"];
4
- type SeedChoice = (typeof seedChoices)[number];
5
- export declare const seedByPool: (pool: DatabasePool, type: SeedChoice) => Promise<void>;
3
+ export declare const seedByPool: (pool: DatabasePool, cloud?: boolean) => Promise<void>;
6
4
  declare const seed: CommandModule<Record<string, unknown>, {
7
- type: string;
8
5
  swe?: boolean;
6
+ cloud?: boolean;
9
7
  }>;
10
8
  export default seed;
@@ -4,25 +4,22 @@ import { doesConfigsTableExist } from '../../../queries/logto-config.js';
4
4
  import { log, oraPromise } from '../../../utils.js';
5
5
  import { getLatestAlterationTimestamp } from '../alteration/index.js';
6
6
  import { getAlterationDirectory } from '../alteration/utils.js';
7
- import { createTables, seedTables } from './tables.js';
8
- const seedChoices = Object.freeze(['all', 'oidc']);
9
- export const seedByPool = async (pool, type) => {
7
+ import { createTables, seedCloud, seedTables } from './tables.js';
8
+ export const seedByPool = async (pool, cloud = false) => {
10
9
  await pool.transaction(async (connection) => {
11
- if (type !== 'oidc') {
12
- // Check alteration scripts available in order to insert correct timestamp
13
- const latestTimestamp = await getLatestAlterationTimestamp();
14
- if (latestTimestamp < 1) {
15
- throw new Error(`No alteration script found when seeding the database.\n` +
16
- `Please check \`${getAlterationDirectory()}\` to see if there are alteration scripts available.\n`);
17
- }
18
- await oraPromise(createTables(connection), {
19
- text: 'Create tables',
20
- prefixText: chalk.blue('[info]'),
21
- });
22
- await oraPromise(seedTables(connection, latestTimestamp), {
23
- text: 'Seed data',
24
- prefixText: chalk.blue('[info]'),
25
- });
10
+ // Check alteration scripts available in order to insert correct timestamp
11
+ const latestTimestamp = await getLatestAlterationTimestamp();
12
+ if (latestTimestamp < 1) {
13
+ throw new Error(`No alteration script found when seeding the database.\n` +
14
+ `Please check \`${getAlterationDirectory()}\` to see if there are alteration scripts available.\n`);
15
+ }
16
+ await oraPromise(createTables(connection), {
17
+ text: 'Create tables',
18
+ prefixText: chalk.blue('[info]'),
19
+ });
20
+ await seedTables(connection, latestTimestamp);
21
+ if (cloud) {
22
+ await seedCloud(connection);
26
23
  }
27
24
  });
28
25
  };
@@ -35,13 +32,12 @@ const seed = {
35
32
  alias: 'skip-when-exists',
36
33
  type: 'boolean',
37
34
  })
38
- .positional('type', {
39
- describe: 'Optional seed type',
40
- type: 'string',
41
- choices: seedChoices,
42
- default: 'all',
35
+ .option('cloud', {
36
+ describe: 'Seed additional cloud data',
37
+ type: 'boolean',
38
+ hidden: true,
43
39
  }),
44
- handler: async ({ type, swe }) => {
40
+ handler: async ({ swe, cloud }) => {
45
41
  const pool = await createPoolAndDatabaseIfNeeded();
46
42
  if (swe && (await doesConfigsTableExist(pool))) {
47
43
  log.info('Seeding skipped');
@@ -49,10 +45,7 @@ const seed = {
49
45
  return;
50
46
  }
51
47
  try {
52
- // Cannot avoid `as` since the official type definition of `yargs` doesn't work.
53
- // The value of `type` can be ensured, so it's safe to use `as` here.
54
- // eslint-disable-next-line no-restricted-syntax
55
- await seedByPool(pool, type);
48
+ await seedByPool(pool, cloud);
56
49
  }
57
50
  catch (error) {
58
51
  console.error(error);
@@ -1,3 +1,4 @@
1
1
  import type { DatabaseTransactionConnection } from 'slonik';
2
2
  export declare const createTables: (connection: DatabaseTransactionConnection) => Promise<void>;
3
3
  export declare const seedTables: (connection: DatabaseTransactionConnection, latestTimestamp: number) => Promise<void>;
4
+ export declare const seedCloud: (connection: DatabaseTransactionConnection) => Promise<void>;
@@ -1,15 +1,16 @@
1
1
  import { readdir, readFile } from 'fs/promises';
2
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';
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
5
  import { sql } from 'slonik';
6
6
  import { raw } from 'slonik-sql-tag-raw';
7
7
  import { insertInto } from '../../../database.js';
8
8
  import { getDatabaseName } from '../../../queries/database.js';
9
9
  import { updateDatabaseTimestamp } from '../../../queries/system.js';
10
- import { getPathInModule } from '../../../utils.js';
10
+ import { getPathInModule, log } from '../../../utils.js';
11
+ import { appendAdminConsoleRedirectUris } from './cloud.js';
11
12
  import { seedOidcConfigs } from './oidc-config.js';
12
- import { createTenant, seedAdminData } from './tenant.js';
13
+ import { assignScopesToRole, createTenant, seedAdminData } from './tenant.js';
13
14
  const getExplicitOrder = (query) => {
14
15
  const matched = /\/\*\s*init_order\s*=\s*([\d.]+)\s*\*\//.exec(query)?.[1];
15
16
  return matched ? Number(matched) : undefined;
@@ -54,7 +55,6 @@ export const createTables = async (connection) => {
54
55
  }
55
56
  };
56
57
  const allQueries = [
57
- [Hooks.tableName, Hooks.raw],
58
58
  [Tenants.tableName, Tenants.raw],
59
59
  ...queries.filter(([file]) => !lifecycleNames.includes(file.slice(1, -4))),
60
60
  ];
@@ -78,12 +78,32 @@ export const seedTables = async (connection, latestTimestamp) => {
78
78
  await createTenant(connection, adminTenantId);
79
79
  await seedOidcConfigs(connection, adminTenantId);
80
80
  await seedAdminData(connection, createAdminDataInAdminTenant(defaultTenantId));
81
+ const adminAdminData = createAdminDataInAdminTenant(adminTenantId);
82
+ await seedAdminData(connection, adminAdminData);
81
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));
82
97
  await Promise.all([
83
98
  connection.query(insertInto(createDefaultAdminConsoleConfig(defaultTenantId), 'logto_configs')),
84
99
  connection.query(insertInto(createDefaultSignInExperience(defaultTenantId), 'sign_in_experiences')),
100
+ connection.query(insertInto(createDefaultAdminConsoleConfig(adminTenantId), 'logto_configs')),
85
101
  connection.query(insertInto(createAdminTenantSignInExperience(), 'sign_in_experiences')),
86
102
  connection.query(insertInto(createDefaultAdminConsoleApplication(), 'applications')),
87
103
  updateDatabaseTimestamp(connection, latestTimestamp),
88
104
  ]);
105
+ log.succeed('Seed data');
106
+ };
107
+ export const seedCloud = async (connection) => {
108
+ await appendAdminConsoleRedirectUris(connection);
89
109
  };
@@ -1,4 +1,5 @@
1
- import type { AdminData } from '@logto/schemas';
1
+ import type { AdminData, UpdateAdminData, CreateScope } from '@logto/schemas';
2
2
  import type { CommonQueryMethods } from 'slonik';
3
3
  export declare const createTenant: (pool: CommonQueryMethods, tenantId: string) => Promise<void>;
4
- export declare const seedAdminData: (pool: CommonQueryMethods, data: AdminData) => 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>;
@@ -16,16 +16,38 @@ export const createTenant = async (pool, tenantId) => {
16
16
  in role ${sql.identifier([parentRole])};
17
17
  `);
18
18
  };
19
- export const seedAdminData = async (pool, data) => {
19
+ export const seedAdminData = async (pool, data, ...additionalScopes) => {
20
20
  const { resource, scope, role } = data;
21
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
+ };
22
35
  await pool.query(insertInto(resource, 'resources'));
23
36
  await pool.query(insertInto(scope, 'scopes'));
24
- await pool.query(insertInto(role, 'roles'));
37
+ await Promise.all(additionalScopes.map(async (scope) => pool.query(insertInto(scope, 'scopes'))));
38
+ const roleId = await processRole();
25
39
  await pool.query(insertInto({
26
40
  id: generateStandardId(),
27
- roleId: role.id,
41
+ roleId,
28
42
  scopeId: scope.id,
29
43
  tenantId: resource.tenantId,
30
44
  }, 'roles_scopes'));
31
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,3 @@
1
+ import type { CommandModule } from 'yargs';
2
+ declare const system: CommandModule;
3
+ export default system;
@@ -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;
@@ -2,13 +2,13 @@ import type { CommandModule } from 'yargs';
2
2
  export type InstallArgs = {
3
3
  path?: string;
4
4
  skipSeed: boolean;
5
- officialConnectors?: boolean;
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
- oc?: boolean;
11
+ cloud: boolean;
12
12
  du?: string;
13
13
  }>;
14
14
  export default install;
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import { getDatabaseUrlFromConfig } from '../../database.js';
3
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, officialConnectors, downloadUrl }) => {
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
- oc: {
42
- alias: 'official-connectors',
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, oc, du }) => {
54
- await installLogto({ path: p, skipSeed: ss, officialConnectors: oc, downloadUrl: du });
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;
@@ -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, 'all');
98
+ await seedByPool(pool);
99
99
  await pool.end();
100
100
  }
101
101
  catch (error) {
@@ -0,0 +1,7 @@
1
+ export declare const notImplemented: () => never;
2
+ export declare const defaultConnectorMethods: {
3
+ getAuthorizationUri: () => never;
4
+ getUserInfo: () => never;
5
+ sendMessage: () => never;
6
+ validateConfig: () => never;
7
+ };
@@ -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,2 @@
1
+ import type { ConnectorFactory, ConnectorPackage } from './types.js';
2
+ export declare const loadConnectorFactories: (connectorPackages: ConnectorPackage[], ignoreVersionMismatch: boolean) => Promise<ConnectorFactory<import("@logto/connector-kit").AllConnector>[]>;
@@ -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,4 @@
1
+ export * from './factories.js';
2
+ export * from './types.js';
3
+ export * from './consts.js';
4
+ export * from './utils.js';
@@ -0,0 +1,4 @@
1
+ export * from './factories.js';
2
+ export * from './types.js';
3
+ export * from './consts.js';
4
+ export * from './utils.js';
@@ -0,0 +1,2 @@
1
+ import type { AllConnector, CreateConnector } from '@logto/connector-kit';
2
+ export declare const loadConnector: (connectorPath: string, ignoreVersionMismatch: boolean) => Promise<CreateConnector<AllConnector>>;
@@ -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
+ };