@logto/cli 1.14.0 → 1.16.0

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 (35) hide show
  1. package/lib/commands/connector/utils.js +2 -2
  2. package/lib/commands/database/alteration/index.d.ts +1 -1
  3. package/lib/commands/database/alteration/index.test.js +11 -8
  4. package/lib/commands/database/seed/cloud.d.ts +1 -1
  5. package/lib/commands/database/seed/cloud.js +1 -1
  6. package/lib/commands/database/seed/index.d.ts +1 -1
  7. package/lib/commands/database/seed/oidc-config.d.ts +1 -1
  8. package/lib/commands/database/seed/tables.d.ts +1 -1
  9. package/lib/commands/database/seed/tables.js +14 -7
  10. package/lib/commands/database/seed/tenant-organizations.d.ts +1 -1
  11. package/lib/commands/database/seed/tenant.d.ts +1 -1
  12. package/lib/commands/database/seed/tenant.js +2 -3
  13. package/lib/commands/install/utils.js +2 -2
  14. package/lib/commands/translate/openai.js +3 -2
  15. package/lib/connector/factories.d.ts +1 -1
  16. package/lib/connector/factories.js +3 -3
  17. package/lib/connector/types.d.ts +1 -1
  18. package/lib/connector/utils.d.ts +1 -1
  19. package/lib/database.d.ts +3 -3
  20. package/lib/database.js +4 -5
  21. package/lib/package-json.d.ts +4 -7
  22. package/lib/package-json.js +20 -23
  23. package/lib/queries/database.d.ts +1 -1
  24. package/lib/queries/database.js +1 -1
  25. package/lib/queries/logto-config.d.ts +3 -3
  26. package/lib/queries/logto-config.js +2 -2
  27. package/lib/queries/system.d.ts +2 -2
  28. package/lib/queries/system.js +2 -2
  29. package/lib/queries/system.test.js +7 -7
  30. package/lib/sql.d.ts +27 -0
  31. package/lib/sql.js +52 -0
  32. package/lib/test-utils.d.ts +2 -2
  33. package/lib/test-utils.js +1 -0
  34. package/lib/utils.js +1 -1
  35. package/package.json +20 -23
@@ -8,7 +8,7 @@ import chalk from 'chalk';
8
8
  import { got } from 'got';
9
9
  import pLimit from 'p-limit';
10
10
  import pRetry from 'p-retry';
11
- import tar from 'tar';
11
+ import { extract } from 'tar';
12
12
  import { z } from 'zod';
13
13
  import { connectorDirectory, coreDirectory } from '../../constants.js';
14
14
  import { consoleLog, getConnectorPackagesFromDirectory, inquireInstancePath, oraPromise, } from '../../utils.js';
@@ -68,7 +68,7 @@ export const addConnectorsToPath = async (cwd, packageNames) => {
68
68
  const packageDirectory = path.join(cwd, name.replaceAll('/', '-'));
69
69
  await fs.rm(packageDirectory, { force: true, recursive: true });
70
70
  await fs.mkdir(packageDirectory, { recursive: true });
71
- await tar.extract({ cwd: packageDirectory, file: tarPath, strip: 1 });
71
+ await extract({ cwd: packageDirectory, file: tarPath, strip: 1 });
72
72
  await fs.unlink(tarPath);
73
73
  consoleLog.succeed(`Added ${chalk.green(name)} v${version}`);
74
74
  };
@@ -1,4 +1,4 @@
1
- import type { DatabasePool } from 'slonik';
1
+ import type { DatabasePool } from '@silverhand/slonik';
2
2
  import type { CommandModule } from 'yargs';
3
3
  import type { AlterationFile } from './type.js';
4
4
  export declare const getLatestAlterationTimestamp: () => Promise<number>;
@@ -1,22 +1,25 @@
1
- import { createMockUtils } from '@logto/shared/esm';
1
+ import { createMockPool } from '@silverhand/slonik';
2
2
  import Sinon from 'sinon';
3
- import { createMockPool } from 'slonik';
3
+ import { vi, expect, afterAll, describe, it } from 'vitest';
4
4
  import { chooseAlterationsByVersion } from './version.js';
5
- const { jest } = import.meta;
6
- const { mockEsmWithActual } = createMockUtils(jest);
7
5
  const pool = createMockPool({
8
- query: jest.fn(),
6
+ query: vi.fn(),
9
7
  });
10
8
  const files = Object.freeze([
11
9
  { filename: '1.0.0-1663923770-a.js', path: '/alterations-js/1.0.0-1663923770-a.js' },
12
10
  { filename: '1.0.0-1663923771-b.js', path: '/alterations-js/1.0.0-1663923771-b.js' },
13
11
  { filename: '1.0.0-1663923772-c.js', path: '/alterations-js/1.0.0-1663923772-c.js' },
14
12
  ]);
15
- await mockEsmWithActual('./utils.js', () => ({
13
+ vi.mock('./utils.js', async (importOriginal) => ({
14
+ // eslint-disable-next-line @typescript-eslint/ban-types
15
+ ...(await importOriginal()),
16
16
  getAlterationFiles: async () => files,
17
17
  }));
18
- const { getCurrentDatabaseAlterationTimestamp } = await mockEsmWithActual('../../../queries/system.js', () => ({
19
- getCurrentDatabaseAlterationTimestamp: jest.fn(),
18
+ const getCurrentDatabaseAlterationTimestamp = vi.fn();
19
+ vi.doMock('../../../queries/system.js', async (importOriginal) => ({
20
+ // eslint-disable-next-line @typescript-eslint/ban-types
21
+ ...(await importOriginal()),
22
+ getCurrentDatabaseAlterationTimestamp,
20
23
  }));
21
24
  const { getAvailableAlterations } = await import('./index.js');
22
25
  describe('getAvailableAlterations()', () => {
@@ -1,4 +1,4 @@
1
- import type { CommonQueryMethods } from 'slonik';
1
+ import type { CommonQueryMethods } from '@silverhand/slonik';
2
2
  /**
3
3
  * Append Redirect URIs for the default tenant callback in cloud Admin Console.
4
4
  * It reads the same env variables as core to construct the cloud `UrlSet`.
@@ -1,7 +1,7 @@
1
1
  import { adminConsoleApplicationId, adminTenantId, defaultTenantId, createTenantMachineToMachineApplication, createAdminTenantApplicationRole, createCloudConnectionConfig, AdminTenantRole, } from '@logto/schemas';
2
2
  import { GlobalValues } from '@logto/shared';
3
3
  import { appendPath } from '@silverhand/essentials';
4
- import { sql } from 'slonik';
4
+ import { sql } from '@silverhand/slonik';
5
5
  import { insertInto } from '../../../database.js';
6
6
  import { consoleLog } from '../../../utils.js';
7
7
  /**
@@ -1,4 +1,4 @@
1
- import type { DatabasePool } from 'slonik';
1
+ import type { DatabasePool } from '@silverhand/slonik';
2
2
  import type { CommandModule } from 'yargs';
3
3
  export declare const seedByPool: (pool: DatabasePool, cloud?: boolean, test?: boolean, encryptBaseRole?: boolean) => Promise<void>;
4
4
  declare const seed: CommandModule<Record<string, unknown>, {
@@ -1,6 +1,6 @@
1
1
  import type { LogtoOidcConfigType } from '@logto/schemas';
2
2
  import { LogtoOidcConfigKey } from '@logto/schemas';
3
- import type { DatabaseTransactionConnection } from 'slonik';
3
+ import type { DatabaseTransactionConnection } from '@silverhand/slonik';
4
4
  export declare const seedOidcConfigs: (pool: DatabaseTransactionConnection, tenantId: string) => Promise<void>;
5
5
  /**
6
6
  * Each config reader will do the following things in order:
@@ -1,4 +1,4 @@
1
- import type { DatabaseTransactionConnection } from 'slonik';
1
+ import type { DatabaseTransactionConnection } from '@silverhand/slonik';
2
2
  export declare const createTables: (connection: DatabaseTransactionConnection, encryptBaseRole: boolean) => Promise<{
3
3
  password: string;
4
4
  }>;
@@ -3,12 +3,12 @@ import path from 'node:path';
3
3
  import { createDefaultAdminConsoleConfig, defaultTenantId, adminTenantId, defaultManagementApi, createAdminDataInAdminTenant, createMeApiInAdminTenant, createDefaultSignInExperience, createAdminTenantSignInExperience, createDefaultAdminConsoleApplication, createCloudApi, createTenantApplicationRole, CloudScope, Roles, UsersRoles, LogtoConfigs, SignInExperiences, Applications, OrganizationUserRelations, getTenantOrganizationId, Users, OrganizationRoleUserRelations, TenantRole, } from '@logto/schemas';
4
4
  import { getTenantRole } from '@logto/schemas';
5
5
  import { Tenants } from '@logto/schemas/models';
6
- import { convertToIdentifiers, generateStandardId } from '@logto/shared';
7
- import { sql } from 'slonik';
8
- import { raw } from 'slonik-sql-tag-raw';
6
+ import { generateStandardId } from '@logto/shared';
7
+ import { sql } from '@silverhand/slonik';
9
8
  import { insertInto } from '../../../database.js';
10
9
  import { getDatabaseName } from '../../../queries/database.js';
11
10
  import { updateDatabaseTimestamp } from '../../../queries/system.js';
11
+ import { convertToIdentifiers } from '../../../sql.js';
12
12
  import { consoleLog, getPathInModule } from '../../../utils.js';
13
13
  import { appendAdminConsoleRedirectUris, seedTenantCloudServiceApplication } from './cloud.js';
14
14
  import { seedOidcConfigs } from './oidc-config.js';
@@ -48,7 +48,7 @@ export const createTables = async (connection, encryptBaseRole) => {
48
48
  const runLifecycleQuery = async (lifecycle, parameters = {}) => {
49
49
  const query = queries.find(([file]) => file.slice(1, -4) === lifecycle)?.[1];
50
50
  if (query) {
51
- await connection.query(sql `${raw(
51
+ await connection.query(sql `${sql.raw(
52
52
  /* eslint-disable no-template-curly-in-string */
53
53
  query
54
54
  .replaceAll('${name}', parameters.name ?? '')
@@ -68,7 +68,7 @@ export const createTables = async (connection, encryptBaseRole) => {
68
68
  await runLifecycleQuery('before_all', { database, password });
69
69
  /* eslint-disable no-await-in-loop */
70
70
  for (const [file, query] of sorted) {
71
- await connection.query(sql `${raw(query)}`);
71
+ await connection.query(sql `${sql.raw(query)}`);
72
72
  if (!query.includes('/* no_after_each */')) {
73
73
  await runLifecycleQuery('after_each', { name: file.split('.')[0], database });
74
74
  }
@@ -93,7 +93,9 @@ export const seedTables = async (connection, latestTimestamp, isCloud) => {
93
93
  const applicationRole = createTenantApplicationRole();
94
94
  await connection.query(insertInto(applicationRole, Roles.table));
95
95
  await assignScopesToRole(connection, adminTenantId, applicationRole.id, ...cloudAdditionalScopes
96
- .filter(({ name }) => name === CloudScope.SendSms || name === CloudScope.SendEmail)
96
+ .filter(({ name }) => name === CloudScope.SendSms ||
97
+ name === CloudScope.SendEmail ||
98
+ name === CloudScope.FetchCustomJwt)
97
99
  .map(({ id }) => id));
98
100
  await Promise.all([
99
101
  seedLegacyManagementApiUserRole(connection),
@@ -103,10 +105,15 @@ export const seedTables = async (connection, latestTimestamp, isCloud) => {
103
105
  connection.query(insertInto(createDefaultSignInExperience(defaultTenantId, isCloud), SignInExperiences.table)),
104
106
  connection.query(insertInto(createAdminTenantSignInExperience(), SignInExperiences.table)),
105
107
  connection.query(insertInto(createDefaultAdminConsoleApplication(), Applications.table)),
106
- updateDatabaseTimestamp(connection, latestTimestamp),
108
+ ]);
109
+ // The below seed data is for the Logto Cloud only. We put it here for the sack of simplicity.
110
+ // The data is not harmful for OSS, since they are all admin tenant data. OSS will not use them
111
+ // and they cannot be seen by the Console.
112
+ await Promise.all([
107
113
  seedTenantOrganizations(connection),
108
114
  seedManagementApiProxyApplications(connection),
109
115
  ]);
116
+ await updateDatabaseTimestamp(connection, latestTimestamp);
110
117
  consoleLog.succeed('Seed data');
111
118
  };
112
119
  export const seedCloud = async (connection) => {
@@ -1,4 +1,4 @@
1
- import type { DatabaseTransactionConnection } from 'slonik';
1
+ import type { DatabaseTransactionConnection } from '@silverhand/slonik';
2
2
  /**
3
3
  * Seed initial data in the admin tenant for tenant organizations (`default` and `admin`):
4
4
  *
@@ -1,5 +1,5 @@
1
1
  import { type AdminData, type UpdateAdminData, type CreateScope } from '@logto/schemas';
2
- import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik';
2
+ import type { CommonQueryMethods, DatabaseTransactionConnection } from '@silverhand/slonik';
3
3
  export declare const createTenant: (pool: CommonQueryMethods, tenantId: string) => Promise<void>;
4
4
  export declare const seedAdminData: (pool: CommonQueryMethods, data: AdminData | UpdateAdminData, ...additionalScopes: CreateScope[]) => Promise<void>;
5
5
  export declare const assignScopesToRole: (pool: CommonQueryMethods, tenantId: string, roleId: string, ...scopeIds: string[]) => Promise<void>;
@@ -2,8 +2,7 @@ import { createTenantMetadata } from '@logto/core-kit';
2
2
  import { defaultTenantId, adminTenantId, Applications, ApplicationsRoles, getMapiProxyM2mApp, getMapiProxyRole, defaultManagementApiAdminName, Roles, PredefinedScope, getManagementApiResourceIndicator, } from '@logto/schemas';
3
3
  import { generateStandardId } from '@logto/shared';
4
4
  import { assert } from '@silverhand/essentials';
5
- import { sql } from 'slonik';
6
- import { raw } from 'slonik-sql-tag-raw';
5
+ import { sql } from '@silverhand/slonik';
7
6
  import { insertInto } from '../../../database.js';
8
7
  import { getDatabaseName } from '../../../queries/database.js';
9
8
  import { consoleLog } from '../../../utils.js';
@@ -18,7 +17,7 @@ export const createTenant = async (pool, tenantId) => {
18
17
  await pool.query(insertInto(createTenant, 'tenants'));
19
18
  await pool.query(sql `
20
19
  create role ${sql.identifier([role])} with inherit login
21
- password '${raw(password)}'
20
+ password '${sql.raw(password)}'
22
21
  in role ${sql.identifier([parentRole])};
23
22
  `);
24
23
  };
@@ -8,7 +8,7 @@ import chalk from 'chalk';
8
8
  import { got, RequestError } from 'got';
9
9
  import inquirer from 'inquirer';
10
10
  import * as semver from 'semver';
11
- import tar from 'tar';
11
+ import { extract } from 'tar';
12
12
  import { defaultPath } from '../../constants.js';
13
13
  import { createPoolAndDatabaseIfNeeded } from '../../database.js';
14
14
  import { packageJson } from '../../package-json.js';
@@ -98,7 +98,7 @@ export const decompress = async (toPath, tarPath) => {
98
98
  const run = async () => {
99
99
  try {
100
100
  await fs.mkdir(toPath);
101
- await tar.extract({ file: tarPath, cwd: toPath, strip: 1 });
101
+ await extract({ file: tarPath, cwd: toPath, strip: 1 });
102
102
  }
103
103
  catch (error) {
104
104
  consoleLog.fatal(error);
@@ -8,7 +8,7 @@ import { getTranslationPromptMessages } from './prompts.js';
8
8
  export const createOpenaiApi = () => {
9
9
  const proxy = getProxy();
10
10
  return got.extend({
11
- prefixUrl: 'https://api.openai.com/v1',
11
+ prefixUrl: process.env.OPENAI_API_PROXY_ENDPOINT ?? 'https://api.openai.com/v1',
12
12
  headers: {
13
13
  Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ''}`,
14
14
  },
@@ -29,7 +29,8 @@ export const translate = async ({ api, targetLanguage, sourceFilePath, extraProm
29
29
  const response = await trySafe(api
30
30
  .post('chat/completions', {
31
31
  json: {
32
- model: 'gpt-3.5-turbo-1106',
32
+ // The full list of OPENAI model can be found at https://platform.openai.com/docs/models.
33
+ model: process.env.OPENAI_MODEL_NAME ?? 'gpt-3.5-turbo-0125',
33
34
  messages: getTranslationPromptMessages({
34
35
  sourceFileContent,
35
36
  targetLanguage,
@@ -1,3 +1,3 @@
1
1
  import type { BaseRoutes, Router } from '@withtyped/server';
2
2
  import type { ConnectorFactory, ConnectorPackage } from './types.js';
3
- export declare const loadConnectorFactories: (connectorPackages: ConnectorPackage[], ignoreVersionMismatch: boolean) => Promise<ConnectorFactory<Router<any, BaseRoutes, string>>[]>;
3
+ export declare const loadConnectorFactories: (connectorPackages: ConnectorPackage[], ignoreVersionMismatch: boolean) => Promise<ConnectorFactory<Router<any, any, BaseRoutes, string>>[]>;
@@ -3,7 +3,7 @@ import { consoleLog } from '../utils.js';
3
3
  import { notImplemented } from './consts.js';
4
4
  import { loadConnector } from './loader.js';
5
5
  import { parseMetadata, validateConnectorModule } from './utils.js';
6
- // eslint-disable-next-line @silverhand/fp/no-let, @typescript-eslint/no-explicit-any
6
+ // eslint-disable-next-line @silverhand/fp/no-let
7
7
  let cachedConnectorFactories;
8
8
  export const loadConnectorFactories = async (connectorPackages, ignoreVersionMismatch) => {
9
9
  if (cachedConnectorFactories) {
@@ -31,8 +31,8 @@ export const loadConnectorFactories = async (connectorPackages, ignoreVersionMis
31
31
  }
32
32
  }));
33
33
  // eslint-disable-next-line @silverhand/fp/no-mutation
34
- cachedConnectorFactories = connectorFactories.filter(
34
+ cachedConnectorFactories = connectorFactories.filter((connectorFactory
35
35
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- (connectorFactory) => connectorFactory !== undefined);
36
+ ) => connectorFactory !== undefined);
37
37
  return cachedConnectorFactories;
38
38
  };
@@ -3,7 +3,7 @@ import type { BaseRoutes, Router } from '@withtyped/server';
3
3
  /**
4
4
  * Dynamic loaded connector type.
5
5
  */
6
- export type ConnectorFactory<T extends Router<any, BaseRoutes, string>, U extends AllConnector = AllConnector> = Pick<U, 'type' | 'metadata' | 'configGuard'> & {
6
+ export type ConnectorFactory<T extends Router<any, any, BaseRoutes, string>, U extends AllConnector = AllConnector> = Pick<U, 'type' | 'metadata' | 'configGuard'> & {
7
7
  createConnector: CreateConnector<U, T>;
8
8
  path: string;
9
9
  };
@@ -5,7 +5,7 @@ import type { ConnectorFactory } from './types.js';
5
5
  export declare function validateConnectorModule(connector: Partial<BaseConnector<ConnectorType>>): asserts connector is BaseConnector<ConnectorType>;
6
6
  export declare const readUrl: (url: string, baseUrl: string, type: 'text' | 'image') => Promise<string>;
7
7
  export declare const parseMetadata: (metadata: AllConnector['metadata'], packagePath: string) => Promise<AllConnector['metadata']>;
8
- export declare const buildRawConnector: <T extends Router<any, BaseRoutes, string>, U extends AllConnector = AllConnector>(connectorFactory: ConnectorFactory<T, U>, getConnectorConfig?: GetConnectorConfig, getCloudServiceClient?: GetCloudServiceClient<T> | undefined) => Promise<{
8
+ export declare const buildRawConnector: <T extends Router<any, any, BaseRoutes, string>, U extends AllConnector = AllConnector>(connectorFactory: ConnectorFactory<T, U>, getConnectorConfig?: GetConnectorConfig, getCloudServiceClient?: GetCloudServiceClient<T> | undefined) => Promise<{
9
9
  rawConnector: U;
10
10
  rawMetadata: ConnectorMetadata;
11
11
  }>;
package/lib/database.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import type { SchemaLike } from '@logto/schemas';
2
2
  export declare const defaultDatabaseUrl = "postgresql://localhost:5432/logto";
3
3
  export declare const getDatabaseUrlFromConfig: () => Promise<string>;
4
- export declare const createPoolFromConfig: () => Promise<import("slonik").DatabasePool>;
4
+ export declare const createPoolFromConfig: () => Promise<import("@silverhand/slonik").DatabasePool>;
5
5
  /**
6
6
  * Create a database pool with the URL in CLI config; if no URL found, prompt to input.
7
7
  * If the given database does not exists, it will try to create a new database by connecting to the maintenance database `postgres`.
8
8
  *
9
9
  * @returns A new database pool with the database URL in config.
10
10
  */
11
- export declare const createPoolAndDatabaseIfNeeded: () => Promise<import("slonik").DatabasePool>;
11
+ export declare const createPoolAndDatabaseIfNeeded: () => Promise<import("@silverhand/slonik").DatabasePool>;
12
12
  /**
13
13
  * Build an `insert into` query from the given payload. If the payload is an array, it will insert
14
14
  * multiple rows.
15
15
  */
16
- export declare const insertInto: <T extends SchemaLike<string>>(payload: T | T[], table: string) => import("slonik").TaggedTemplateLiteralInvocation<import("slonik").QueryResultRow>;
16
+ export declare const insertInto: <T extends SchemaLike<string>>(payload: T | T[], table: string) => import("@silverhand/slonik").TaggedTemplateLiteralInvocation<import("@silverhand/slonik").QueryResultRow>;
package/lib/database.js CHANGED
@@ -1,9 +1,8 @@
1
- import { convertToPrimitiveOrSql } from '@logto/shared';
2
1
  import { assert } from '@silverhand/essentials';
2
+ import { createPool, parseDsn, sql, stringifyDsn, createInterceptorsPreset, } from '@silverhand/slonik';
3
3
  import decamelize from 'decamelize';
4
4
  import { DatabaseError } from 'pg-protocol';
5
- import { createPool, parseDsn, sql, stringifyDsn } from 'slonik';
6
- import { createInterceptors } from 'slonik-interceptor-preset';
5
+ import { convertToPrimitiveOrSql } from './sql.js';
7
6
  import { ConfigKey, consoleLog, getCliConfigWithPrompt } from './utils.js';
8
7
  export const defaultDatabaseUrl = 'postgresql://localhost:5432/logto';
9
8
  export const getDatabaseUrlFromConfig = async () => (await getCliConfigWithPrompt({
@@ -15,7 +14,7 @@ export const createPoolFromConfig = async () => {
15
14
  const databaseUrl = await getDatabaseUrlFromConfig();
16
15
  assert(parseDsn(databaseUrl).databaseName, new Error('Database name is required in URL'));
17
16
  return createPool(databaseUrl, {
18
- interceptors: createInterceptors(),
17
+ interceptors: createInterceptorsPreset(),
19
18
  });
20
19
  };
21
20
  /**
@@ -41,7 +40,7 @@ export const createPoolAndDatabaseIfNeeded = async () => {
41
40
  // - It will throw error when creating database using '?'
42
41
  const databaseName = dsn.databaseName ?? '?';
43
42
  const maintenancePool = await createPool(stringifyDsn({ ...dsn, databaseName: 'postgres' }), {
44
- interceptors: createInterceptors(),
43
+ interceptors: createInterceptorsPreset(),
45
44
  });
46
45
  await maintenancePool.query(sql `
47
46
  create database ${sql.identifier([databaseName])}
@@ -28,7 +28,6 @@ export declare const packageJson: {
28
28
  "start:dev": string;
29
29
  lint: string;
30
30
  "lint:report": string;
31
- "test:only": string;
32
31
  test: string;
33
32
  "test:ci": string;
34
33
  prepack: string;
@@ -48,6 +47,7 @@ export declare const packageJson: {
48
47
  "@logto/schemas": string;
49
48
  "@logto/shared": string;
50
49
  "@silverhand/essentials": string;
50
+ "@silverhand/slonik": string;
51
51
  chalk: string;
52
52
  decamelize: string;
53
53
  dotenv: string;
@@ -62,9 +62,6 @@ export declare const packageJson: {
62
62
  "pg-protocol": string;
63
63
  roarr: string;
64
64
  semver: string;
65
- slonik: string;
66
- "slonik-interceptor-preset": string;
67
- "slonik-sql-tag-raw": string;
68
65
  tar: string;
69
66
  typescript: string;
70
67
  yargs: string;
@@ -73,19 +70,19 @@ export declare const packageJson: {
73
70
  devDependencies: {
74
71
  "@silverhand/eslint-config": string;
75
72
  "@silverhand/ts-config": string;
76
- "@withtyped/server": string;
77
73
  "@types/inquirer": string;
78
- "@types/jest": string;
79
74
  "@types/node": string;
80
75
  "@types/semver": string;
81
76
  "@types/sinon": string;
82
77
  "@types/tar": string;
83
78
  "@types/yargs": string;
79
+ "@vitest/coverage-v8": string;
80
+ "@withtyped/server": string;
84
81
  eslint: string;
85
- jest: string;
86
82
  "lint-staged": string;
87
83
  prettier: string;
88
84
  sinon: string;
85
+ vitest: string;
89
86
  };
90
87
  eslintConfig: {
91
88
  extends: string;
@@ -1,6 +1,6 @@
1
1
  export const packageJson = {
2
2
  "name": "@logto/cli",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "Logto CLI.",
5
5
  "author": "Silverhand Inc. <contact@silverhand.io>",
6
6
  "homepage": "https://github.com/logto-io/logto#readme",
@@ -25,15 +25,14 @@ export const packageJson = {
25
25
  "precommit": "lint-staged",
26
26
  "prepare:package-json": "node -p \"'export const packageJson = ' + JSON.stringify(require('./package.json'), undefined, 2) + ';'\" > src/package-json.ts",
27
27
  "build": "rm -rf lib && pnpm prepare:package-json && tsc -p tsconfig.build.json",
28
- "build:test": "rm -rf lib/ && pnpm prepare:package-json && tsc -p tsconfig.test.json --sourcemap",
28
+ "build:test": "pnpm build",
29
29
  "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
30
30
  "start": "node .",
31
31
  "start:dev": "pnpm build && node .",
32
32
  "lint": "eslint --ext .ts src",
33
33
  "lint:report": "pnpm lint --format json --output-file report.json",
34
- "test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
35
- "test": "pnpm build:test && pnpm test:only",
36
- "test:ci": "pnpm test:only",
34
+ "test": "vitest src",
35
+ "test:ci": "pnpm run test --silent --coverage",
37
36
  "prepack": "pnpm build"
38
37
  },
39
38
  "engines": {
@@ -43,14 +42,15 @@ export const packageJson = {
43
42
  "url": "https://github.com/logto-io/logto/issues"
44
43
  },
45
44
  "dependencies": {
46
- "@logto/connector-kit": "workspace:^2.1.0",
47
- "@logto/core-kit": "workspace:^2.3.0",
45
+ "@logto/connector-kit": "workspace:^3.0.0",
46
+ "@logto/core-kit": "workspace:^2.4.0",
48
47
  "@logto/language-kit": "workspace:^1.1.0",
49
- "@logto/phrases": "workspace:^1.9.0",
50
- "@logto/phrases-experience": "workspace:^1.6.0",
51
- "@logto/schemas": "workspace:1.14.0",
52
- "@logto/shared": "workspace:^3.1.0",
48
+ "@logto/phrases": "workspace:^1.10.1",
49
+ "@logto/phrases-experience": "workspace:^1.6.1",
50
+ "@logto/schemas": "workspace:1.16.0",
51
+ "@logto/shared": "workspace:^3.1.1",
53
52
  "@silverhand/essentials": "^2.9.0",
53
+ "@silverhand/slonik": "31.0.0-beta.2",
54
54
  "chalk": "^5.0.0",
55
55
  "decamelize": "^6.0.0",
56
56
  "dotenv": "^16.0.0",
@@ -65,30 +65,27 @@ export const packageJson = {
65
65
  "pg-protocol": "^1.6.0",
66
66
  "roarr": "^7.11.0",
67
67
  "semver": "^7.3.8",
68
- "slonik": "^30.0.0",
69
- "slonik-interceptor-preset": "^1.2.10",
70
- "slonik-sql-tag-raw": "^1.1.4",
71
- "tar": "^6.1.11",
68
+ "tar": "^7.0.0",
72
69
  "typescript": "^5.3.3",
73
70
  "yargs": "^17.6.0",
74
71
  "zod": "^3.22.4"
75
72
  },
76
73
  "devDependencies": {
77
- "@silverhand/eslint-config": "5.0.0",
78
- "@silverhand/ts-config": "5.0.0",
79
- "@withtyped/server": "^0.12.9",
74
+ "@silverhand/eslint-config": "6.0.1",
75
+ "@silverhand/ts-config": "6.0.0",
80
76
  "@types/inquirer": "^9.0.0",
81
- "@types/jest": "^29.4.0",
82
77
  "@types/node": "^20.9.5",
83
78
  "@types/semver": "^7.3.12",
84
79
  "@types/sinon": "^17.0.0",
85
- "@types/tar": "^6.1.2",
80
+ "@types/tar": "^6.1.12",
86
81
  "@types/yargs": "^17.0.13",
87
- "eslint": "^8.44.0",
88
- "jest": "^29.7.0",
82
+ "@vitest/coverage-v8": "^1.4.0",
83
+ "@withtyped/server": "^0.13.6",
84
+ "eslint": "^8.56.0",
89
85
  "lint-staged": "^15.0.0",
90
86
  "prettier": "^3.0.0",
91
- "sinon": "^17.0.0"
87
+ "sinon": "^17.0.0",
88
+ "vitest": "^1.4.0"
92
89
  },
93
90
  "eslintConfig": {
94
91
  "extends": "@silverhand",
@@ -1,2 +1,2 @@
1
- import type { CommonQueryMethods } from 'slonik';
1
+ import type { CommonQueryMethods } from '@silverhand/slonik';
2
2
  export declare const getDatabaseName: (pool: CommonQueryMethods, normalized?: boolean) => Promise<string>;
@@ -1,4 +1,4 @@
1
- import { sql } from 'slonik';
1
+ import { sql } from '@silverhand/slonik';
2
2
  export const getDatabaseName = async (pool, normalized = false) => {
3
3
  const { currentDatabase } = await pool.one(sql `
4
4
  select current_database();
@@ -1,6 +1,6 @@
1
1
  import type { LogtoConfig, LogtoConfigKey, logtoConfigGuards } from '@logto/schemas';
2
- import type { CommonQueryMethods } from 'slonik';
2
+ import type { CommonQueryMethods } from '@silverhand/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, 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>>;
5
+ export declare const getRowsByKeys: (pool: CommonQueryMethods, tenantId: string, keys: LogtoConfigKey[]) => Promise<import("@silverhand/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("@silverhand/slonik").QueryResult<import("@silverhand/slonik").QueryResultRow>>;
@@ -1,6 +1,6 @@
1
1
  import { LogtoConfigs } from '@logto/schemas';
2
- import { convertToIdentifiers } from '@logto/shared';
3
- import { sql } from 'slonik';
2
+ import { sql } from '@silverhand/slonik';
3
+ import { convertToIdentifiers } from '../sql.js';
4
4
  const { table, fields } = convertToIdentifiers(LogtoConfigs);
5
5
  export const doesConfigsTableExist = async (pool) => {
6
6
  const { rows } = await pool.query(sql `select to_regclass(${LogtoConfigs.table}) as regclass`);
@@ -1,9 +1,9 @@
1
1
  import type { System, SystemKey } from '@logto/schemas';
2
2
  import { systemGuards } from '@logto/schemas';
3
- import type { CommonQueryMethods, DatabaseTransactionConnection } from 'slonik';
3
+ import type { CommonQueryMethods, DatabaseTransactionConnection } from '@silverhand/slonik';
4
4
  import type { z } from 'zod';
5
5
  export declare const doesSystemsTableExist: (pool: CommonQueryMethods) => Promise<boolean>;
6
6
  export declare const getCurrentDatabaseAlterationTimestamp: (pool: CommonQueryMethods) => Promise<number>;
7
7
  export declare const updateDatabaseTimestamp: (connection: DatabaseTransactionConnection, timestamp: number) => Promise<void>;
8
8
  export declare const getRowByKey: (pool: CommonQueryMethods, key: SystemKey) => Promise<System | null>;
9
- export declare const updateValueByKey: <T extends SystemKey>(pool: CommonQueryMethods, key: T, value: z.TypeOf<import("@logto/schemas").SystemGuard[T]>) => Promise<import("slonik").QueryResult<import("slonik").QueryResultRow>>;
9
+ export declare const updateValueByKey: <T extends SystemKey>(pool: CommonQueryMethods, key: T, value: z.TypeOf<import("@logto/schemas").SystemGuard[T]>) => Promise<import("@silverhand/slonik").QueryResult<import("@silverhand/slonik").QueryResultRow>>;
@@ -1,7 +1,7 @@
1
1
  import { systemGuards, Systems, AlterationStateKey } from '@logto/schemas';
2
- import { convertToIdentifiers } from '@logto/shared';
2
+ import { sql } from '@silverhand/slonik';
3
3
  import { DatabaseError } from 'pg-protocol';
4
- import { sql } from 'slonik';
4
+ import { convertToIdentifiers } from '../sql.js';
5
5
  const { fields, table } = convertToIdentifiers(Systems);
6
6
  const doesTableExist = async (pool, table) => {
7
7
  const { rows } = await pool.query(sql `select to_regclass(${table}) as regclass`);
@@ -1,11 +1,11 @@
1
1
  import { AlterationStateKey, Systems } from '@logto/schemas';
2
- import { convertToIdentifiers } from '@logto/shared';
2
+ import { createMockPool, createMockQueryResult, sql } from '@silverhand/slonik';
3
3
  import { DatabaseError } from 'pg-protocol';
4
- import { createMockPool, createMockQueryResult, sql } from 'slonik';
4
+ import { describe, it, expect, vi, afterAll, beforeAll } from 'vitest';
5
+ import { convertToIdentifiers } from '../sql.js';
5
6
  import { expectSqlAssert } from '../test-utils.js';
6
7
  import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './system.js';
7
- const { jest } = import.meta;
8
- const mockQuery = jest.fn();
8
+ const mockQuery = vi.fn();
9
9
  const pool = createMockPool({
10
10
  query: async (sql, values) => {
11
11
  return mockQuery(sql, values);
@@ -71,11 +71,11 @@ describe('updateDatabaseTimestamp()', () => {
71
71
  `;
72
72
  const updatedAt = '2022-09-21T06:32:46.583Z';
73
73
  beforeAll(() => {
74
- jest.useFakeTimers();
75
- jest.setSystemTime(new Date(updatedAt));
74
+ vi.useFakeTimers();
75
+ vi.setSystemTime(new Date(updatedAt));
76
76
  });
77
77
  afterAll(() => {
78
- jest.useRealTimers();
78
+ vi.useRealTimers();
79
79
  });
80
80
  it('sends upsert sql with timestamp and updatedAt', async () => {
81
81
  mockQuery
package/lib/sql.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Copied from `@logto/core`. Originally we put them in `@logto/shared` but it
3
+ * requires `slonik` which makes the package too heavy.
4
+ *
5
+ * Since `@logto/cli` only use these functions in a stable manner, we copy them here for now. If
6
+ * the number of functions grows, we should consider moving them to a separate package. (Actually,
7
+ * we should remove the dependency on `slonik` at all, and this may not be an issue then.)
8
+ */
9
+ import { type SchemaValue, type SchemaValuePrimitive, type Table } from '@logto/shared';
10
+ import { type IdentifierSqlToken, type SqlToken } from '@silverhand/slonik';
11
+ /**
12
+ * Note `undefined` is removed from the acceptable list,
13
+ * since you should NOT call this function if ignoring the field is the desired behavior.
14
+ * Calling this function with `null` means an explicit `null` setting in database is expected.
15
+ * @param key The key of value. Will treat as `timestamp` if it ends with `_at` or 'At' AND value is a number;
16
+ * @param value The value to convert.
17
+ * @returns A primitive that can be saved into database.
18
+ */
19
+ export declare const convertToPrimitiveOrSql: (key: string, value: SchemaValue) => NonNullable<SchemaValuePrimitive> | SqlToken | null;
20
+ type FieldIdentifiers<Key extends string> = {
21
+ [key in Key]: IdentifierSqlToken;
22
+ };
23
+ export declare const convertToIdentifiers: <Key extends string>({ table, fields }: Table<Key>, withPrefix?: boolean) => {
24
+ table: IdentifierSqlToken;
25
+ fields: FieldIdentifiers<Key>;
26
+ };
27
+ export {};
package/lib/sql.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Copied from `@logto/core`. Originally we put them in `@logto/shared` but it
3
+ * requires `slonik` which makes the package too heavy.
4
+ *
5
+ * Since `@logto/cli` only use these functions in a stable manner, we copy them here for now. If
6
+ * the number of functions grows, we should consider moving them to a separate package. (Actually,
7
+ * we should remove the dependency on `slonik` at all, and this may not be an issue then.)
8
+ */
9
+ import { sql } from '@silverhand/slonik';
10
+ /**
11
+ * Note `undefined` is removed from the acceptable list,
12
+ * since you should NOT call this function if ignoring the field is the desired behavior.
13
+ * Calling this function with `null` means an explicit `null` setting in database is expected.
14
+ * @param key The key of value. Will treat as `timestamp` if it ends with `_at` or 'At' AND value is a number;
15
+ * @param value The value to convert.
16
+ * @returns A primitive that can be saved into database.
17
+ */
18
+ export const convertToPrimitiveOrSql = (key, value
19
+ // eslint-disable-next-line @typescript-eslint/ban-types
20
+ ) => {
21
+ if (value === null) {
22
+ return null;
23
+ }
24
+ if (typeof value === 'object') {
25
+ return JSON.stringify(value);
26
+ }
27
+ if ((['_at', 'At'].some((value) => key.endsWith(value)) || key === 'date') &&
28
+ typeof value === 'number') {
29
+ return sql `to_timestamp(${value}::double precision / 1000)`;
30
+ }
31
+ if (typeof value === 'number' || typeof value === 'boolean') {
32
+ return value;
33
+ }
34
+ if (typeof value === 'string') {
35
+ if (value === '') {
36
+ return null;
37
+ }
38
+ return value;
39
+ }
40
+ throw new Error(`Cannot convert ${key} to primitive`);
41
+ };
42
+ export const convertToIdentifiers = ({ table, fields }, withPrefix = false) => {
43
+ const fieldsIdentifiers = Object.entries(fields).map(
44
+ // eslint-disable-next-line no-restricted-syntax -- Object.entries can only return string keys
45
+ ([key, value]) => [key, sql.identifier(withPrefix ? [table, value] : [value])]);
46
+ return {
47
+ table: sql.identifier([table]),
48
+ // Key value inferred from the original fields directly
49
+ // eslint-disable-next-line no-restricted-syntax
50
+ fields: Object.fromEntries(fieldsIdentifiers),
51
+ };
52
+ };
@@ -1,5 +1,5 @@
1
- import type { QueryResult, QueryResultRow } from 'slonik';
2
- import type { PrimitiveValueExpression } from 'slonik/dist/src/types.js';
1
+ import type { QueryResult, QueryResultRow } from '@silverhand/slonik';
2
+ import type { PrimitiveValueExpression } from '@silverhand/slonik/dist/src/types.js';
3
3
  export type QueryType = (sql: string, values: readonly PrimitiveValueExpression[]) => Promise<QueryResult<QueryResultRow>>;
4
4
  /**
5
5
  * Slonik Query Mock Utils
package/lib/test-utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // Copied from core
2
+ import { expect } from 'vitest';
2
3
  /**
3
4
  * Slonik Query Mock Utils
4
5
  **/
package/lib/utils.js CHANGED
@@ -55,7 +55,7 @@ export const downloadFile = async (url, destination) => {
55
55
  });
56
56
  file.on('error', (error) => {
57
57
  spinner.fail();
58
- reject(error.message);
58
+ reject(new Error(error.message));
59
59
  });
60
60
  file.on('finish', () => {
61
61
  file.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/cli",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "Logto CLI.",
5
5
  "author": "Silverhand Inc. <contact@silverhand.io>",
6
6
  "homepage": "https://github.com/logto-io/logto#readme",
@@ -28,14 +28,15 @@
28
28
  "url": "https://github.com/logto-io/logto/issues"
29
29
  },
30
30
  "dependencies": {
31
- "@logto/connector-kit": "^2.1.0",
32
- "@logto/core-kit": "^2.3.0",
31
+ "@logto/connector-kit": "^3.0.0",
32
+ "@logto/core-kit": "^2.4.0",
33
33
  "@logto/language-kit": "^1.1.0",
34
- "@logto/phrases": "^1.9.0",
35
- "@logto/phrases-experience": "^1.6.0",
36
- "@logto/schemas": "1.14.0",
37
- "@logto/shared": "^3.1.0",
34
+ "@logto/phrases": "^1.10.1",
35
+ "@logto/phrases-experience": "^1.6.1",
36
+ "@logto/schemas": "1.16.0",
37
+ "@logto/shared": "^3.1.1",
38
38
  "@silverhand/essentials": "^2.9.0",
39
+ "@silverhand/slonik": "31.0.0-beta.2",
39
40
  "chalk": "^5.0.0",
40
41
  "decamelize": "^6.0.0",
41
42
  "dotenv": "^16.0.0",
@@ -50,30 +51,27 @@
50
51
  "pg-protocol": "^1.6.0",
51
52
  "roarr": "^7.11.0",
52
53
  "semver": "^7.3.8",
53
- "slonik": "^30.0.0",
54
- "slonik-interceptor-preset": "^1.2.10",
55
- "slonik-sql-tag-raw": "^1.1.4",
56
- "tar": "^6.1.11",
54
+ "tar": "^7.0.0",
57
55
  "typescript": "^5.3.3",
58
56
  "yargs": "^17.6.0",
59
57
  "zod": "^3.22.4"
60
58
  },
61
59
  "devDependencies": {
62
- "@silverhand/eslint-config": "5.0.0",
63
- "@silverhand/ts-config": "5.0.0",
64
- "@withtyped/server": "^0.12.9",
60
+ "@silverhand/eslint-config": "6.0.1",
61
+ "@silverhand/ts-config": "6.0.0",
65
62
  "@types/inquirer": "^9.0.0",
66
- "@types/jest": "^29.4.0",
67
63
  "@types/node": "^20.9.5",
68
64
  "@types/semver": "^7.3.12",
69
65
  "@types/sinon": "^17.0.0",
70
- "@types/tar": "^6.1.2",
66
+ "@types/tar": "^6.1.12",
71
67
  "@types/yargs": "^17.0.13",
72
- "eslint": "^8.44.0",
73
- "jest": "^29.7.0",
68
+ "@vitest/coverage-v8": "^1.4.0",
69
+ "@withtyped/server": "^0.13.6",
70
+ "eslint": "^8.56.0",
74
71
  "lint-staged": "^15.0.0",
75
72
  "prettier": "^3.0.0",
76
- "sinon": "^17.0.0"
73
+ "sinon": "^17.0.0",
74
+ "vitest": "^1.4.0"
77
75
  },
78
76
  "eslintConfig": {
79
77
  "extends": "@silverhand",
@@ -89,14 +87,13 @@
89
87
  "precommit": "lint-staged",
90
88
  "prepare:package-json": "node -p \"'export const packageJson = ' + JSON.stringify(require('./package.json'), undefined, 2) + ';'\" > src/package-json.ts",
91
89
  "build": "rm -rf lib && pnpm prepare:package-json && tsc -p tsconfig.build.json",
92
- "build:test": "rm -rf lib/ && pnpm prepare:package-json && tsc -p tsconfig.test.json --sourcemap",
90
+ "build:test": "pnpm build",
93
91
  "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
94
92
  "start": "node .",
95
93
  "start:dev": "pnpm build && node .",
96
94
  "lint": "eslint --ext .ts src",
97
95
  "lint:report": "pnpm lint --format json --output-file report.json",
98
- "test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
99
- "test": "pnpm build:test && pnpm test:only",
100
- "test:ci": "pnpm test:only"
96
+ "test": "vitest src",
97
+ "test:ci": "pnpm run test --silent --coverage"
101
98
  }
102
99
  }