@logto/cli 1.8.0 → 1.9.1
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/lib/commands/connector/add.js +2 -2
- package/lib/commands/connector/link.js +2 -2
- package/lib/commands/connector/utils.d.ts +2 -3
- package/lib/commands/connector/utils.js +3 -50
- package/lib/commands/database/seed/index.js +0 -2
- package/lib/commands/database/seed/oidc-config.js +2 -2
- package/lib/commands/install/index.js +7 -7
- package/lib/commands/install/utils.d.ts +3 -4
- package/lib/commands/install/utils.js +6 -8
- package/lib/commands/translate/create.js +4 -5
- package/lib/commands/translate/list-tags.js +2 -2
- package/lib/commands/translate/prompts.d.ts +2 -1
- package/lib/commands/translate/prompts.js +10 -7
- package/lib/commands/translate/sync-keys/index.js +7 -15
- package/lib/commands/translate/sync-keys/utils.d.ts +6 -5
- package/lib/commands/translate/sync-keys/utils.js +46 -22
- package/lib/commands/translate/sync.js +8 -5
- package/lib/commands/translate/utils.d.ts +1 -1
- package/lib/commands/translate/utils.js +1 -2
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +4 -0
- package/lib/package-json.d.ts +1 -1
- package/lib/package-json.js +8 -8
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +66 -4
- package/package.json +8 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { consoleLog } from '../../utils.js';
|
|
2
|
-
import { addConnectors, addOfficialConnectors
|
|
1
|
+
import { consoleLog, inquireInstancePath } from '../../utils.js';
|
|
2
|
+
import { addConnectors, addOfficialConnectors } from './utils.js';
|
|
3
3
|
const add = {
|
|
4
4
|
command: ['add [packages...]', 'a', 'install', 'i'],
|
|
5
5
|
describe: 'Add specific Logto connectors',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { consoleLog } from '../../utils.js';
|
|
4
|
-
import { getConnectorDirectory, getLocalConnectorPackages
|
|
3
|
+
import { consoleLog, inquireInstancePath } from '../../utils.js';
|
|
4
|
+
import { getConnectorDirectory, getLocalConnectorPackages } from './utils.js';
|
|
5
5
|
const link = {
|
|
6
6
|
command: ['link', 'ln'],
|
|
7
7
|
describe: 'Link all connectors in `packages/connectors`, useful for adding local connectors.',
|
|
@@ -5,14 +5,13 @@ export declare const npmPackResultGuard: z.ZodArray<z.ZodObject<{
|
|
|
5
5
|
filename: z.ZodString;
|
|
6
6
|
}, "strip", z.ZodTypeAny, {
|
|
7
7
|
name: string;
|
|
8
|
-
filename: string;
|
|
9
8
|
version: string;
|
|
9
|
+
filename: string;
|
|
10
10
|
}, {
|
|
11
11
|
name: string;
|
|
12
|
-
filename: string;
|
|
13
12
|
version: string;
|
|
13
|
+
filename: string;
|
|
14
14
|
}>, "many">;
|
|
15
|
-
export declare const inquireInstancePath: (initialPath?: string) => Promise<string>;
|
|
16
15
|
export declare const normalizePackageName: (name: string) => string;
|
|
17
16
|
export declare const getConnectorDirectory: (instancePath: string) => string;
|
|
18
17
|
export declare const isOfficialConnector: (packageName: string) => boolean;
|
|
@@ -3,18 +3,15 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import {
|
|
6
|
+
import { conditionalArray, conditionalString, trySafe } from '@silverhand/essentials';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { got } from 'got';
|
|
9
|
-
import inquirer from 'inquirer';
|
|
10
9
|
import pLimit from 'p-limit';
|
|
11
10
|
import pRetry from 'p-retry';
|
|
12
11
|
import tar from 'tar';
|
|
13
12
|
import { z } from 'zod';
|
|
14
|
-
import { connectorDirectory } from '../../constants.js';
|
|
15
|
-
import { consoleLog, getConnectorPackagesFromDirectory,
|
|
16
|
-
import { defaultPath } from '../install/utils.js';
|
|
17
|
-
const coreDirectory = 'packages/core';
|
|
13
|
+
import { connectorDirectory, coreDirectory } from '../../constants.js';
|
|
14
|
+
import { consoleLog, getConnectorPackagesFromDirectory, inquireInstancePath, oraPromise, } from '../../utils.js';
|
|
18
15
|
const execPromise = promisify(exec);
|
|
19
16
|
export const npmPackResultGuard = z
|
|
20
17
|
.object({
|
|
@@ -23,49 +20,6 @@ export const npmPackResultGuard = z
|
|
|
23
20
|
filename: z.string(),
|
|
24
21
|
})
|
|
25
22
|
.array();
|
|
26
|
-
const buildPathErrorMessage = (value) => `The path ${chalk.green(value)} does not contain a Logto instance, please try another.`;
|
|
27
|
-
const validatePath = async (value) => {
|
|
28
|
-
const corePackageJsonPath = path.resolve(path.join(value, coreDirectory, 'package.json'));
|
|
29
|
-
if (!existsSync(corePackageJsonPath)) {
|
|
30
|
-
return buildPathErrorMessage(value);
|
|
31
|
-
}
|
|
32
|
-
const packageJson = await fs.readFile(corePackageJsonPath, { encoding: 'utf8' });
|
|
33
|
-
const packageName = await z
|
|
34
|
-
.object({ name: z.string() })
|
|
35
|
-
.parseAsync(JSON.parse(packageJson))
|
|
36
|
-
.then(({ name }) => name)
|
|
37
|
-
.catch(() => '');
|
|
38
|
-
if (packageName !== '@logto/core') {
|
|
39
|
-
return buildPathErrorMessage(value);
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
};
|
|
43
|
-
export const inquireInstancePath = async (initialPath) => {
|
|
44
|
-
const inquire = async () => {
|
|
45
|
-
if (!initialPath && (await validatePath('.')) === true) {
|
|
46
|
-
return path.resolve('.');
|
|
47
|
-
}
|
|
48
|
-
if (!isTty()) {
|
|
49
|
-
assert(initialPath, new Error('Path is missing'));
|
|
50
|
-
return initialPath;
|
|
51
|
-
}
|
|
52
|
-
const { instancePath } = await inquirer.prompt({
|
|
53
|
-
name: 'instancePath',
|
|
54
|
-
message: 'Where is your Logto instance?',
|
|
55
|
-
type: 'input',
|
|
56
|
-
default: defaultPath,
|
|
57
|
-
filter: (value) => value.trim(),
|
|
58
|
-
validate: validatePath,
|
|
59
|
-
}, { instancePath: initialPath });
|
|
60
|
-
return instancePath;
|
|
61
|
-
};
|
|
62
|
-
const instancePath = await inquire();
|
|
63
|
-
const validated = await validatePath(instancePath);
|
|
64
|
-
if (validated !== true) {
|
|
65
|
-
consoleLog.fatal(validated);
|
|
66
|
-
}
|
|
67
|
-
return instancePath;
|
|
68
|
-
};
|
|
69
23
|
const packagePrefix = 'connector-';
|
|
70
24
|
export const normalizePackageName = (name) => name
|
|
71
25
|
.split('/')
|
|
@@ -172,7 +126,6 @@ export const fetchOfficialConnectorList = async (includingCloudConnectors = fals
|
|
|
172
126
|
export const addOfficialConnectors = async (instancePath, includingCloudConnectors = false) => {
|
|
173
127
|
const packages = await oraPromise(fetchOfficialConnectorList(includingCloudConnectors), {
|
|
174
128
|
text: 'Fetch official connector list',
|
|
175
|
-
prefixText: chalk.blue('[info]'),
|
|
176
129
|
});
|
|
177
130
|
consoleLog.info(`Found ${packages.length} official connectors`);
|
|
178
131
|
await addConnectors(instancePath, packages.map(({ name }) => name));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import { createPoolAndDatabaseIfNeeded } from '../../../database.js';
|
|
3
2
|
import { doesConfigsTableExist } from '../../../queries/logto-config.js';
|
|
4
3
|
import { consoleLog, oraPromise } from '../../../utils.js';
|
|
@@ -15,7 +14,6 @@ export const seedByPool = async (pool, cloud = false) => {
|
|
|
15
14
|
}
|
|
16
15
|
await oraPromise(createTables(connection), {
|
|
17
16
|
text: 'Create tables',
|
|
18
|
-
prefixText: chalk.blue('[info]'),
|
|
19
17
|
});
|
|
20
18
|
await seedTables(connection, latestTimestamp, cloud);
|
|
21
19
|
if (cloud) {
|
|
@@ -36,10 +36,10 @@ export const seedOidcConfigs = async (pool, tenantId) => {
|
|
|
36
36
|
for (const key of validOptions) {
|
|
37
37
|
const { value, fromEnv } = await oidcConfigReaders[key]();
|
|
38
38
|
if (fromEnv) {
|
|
39
|
-
consoleLog.
|
|
39
|
+
consoleLog.succeed(tenantPrefix, `Read config ${chalk.green(key)} from env`);
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
consoleLog.
|
|
42
|
+
consoleLog.succeed(tenantPrefix, `Generated config ${chalk.green(key)}`);
|
|
43
43
|
}
|
|
44
44
|
await updateValueByKey(pool, tenantId, key, value);
|
|
45
45
|
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { getDatabaseUrlFromConfig } from '../../database.js';
|
|
3
3
|
import { consoleLog } from '../../utils.js';
|
|
4
|
-
import { validateNodeVersion,
|
|
4
|
+
import { validateNodeVersion, inquireInstallPath, validateDatabase, downloadRelease, seedDatabase, createEnv, logFinale, decompress, isUrl, } from './utils.js';
|
|
5
5
|
const installLogto = async ({ path, skipSeed, downloadUrl, cloud }) => {
|
|
6
6
|
validateNodeVersion();
|
|
7
|
-
// Get
|
|
8
|
-
const
|
|
7
|
+
// Get install location path
|
|
8
|
+
const installPath = await inquireInstallPath(path);
|
|
9
9
|
// Validate if user has a valid database
|
|
10
10
|
await validateDatabase();
|
|
11
11
|
// Download and decompress
|
|
12
12
|
const tarPath = !downloadUrl || isUrl(downloadUrl) ? await downloadRelease(downloadUrl) : downloadUrl;
|
|
13
|
-
await decompress(
|
|
13
|
+
await decompress(installPath, tarPath);
|
|
14
14
|
// Seed database
|
|
15
15
|
if (skipSeed) {
|
|
16
16
|
consoleLog.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(
|
|
19
|
+
await seedDatabase(installPath, cloud);
|
|
20
20
|
}
|
|
21
21
|
// Save to dot env
|
|
22
|
-
await createEnv(
|
|
22
|
+
await createEnv(installPath, await getDatabaseUrlFromConfig());
|
|
23
23
|
// Finale
|
|
24
|
-
logFinale(
|
|
24
|
+
logFinale(installPath);
|
|
25
25
|
};
|
|
26
26
|
const install = {
|
|
27
27
|
command: ['init', 'i', 'install'],
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export declare const defaultPath: string;
|
|
2
1
|
export declare const validateNodeVersion: () => void;
|
|
3
|
-
export declare const
|
|
2
|
+
export declare const inquireInstallPath: (initialPath?: string) => Promise<string>;
|
|
4
3
|
export declare const validateDatabase: () => Promise<void>;
|
|
5
4
|
export declare const downloadRelease: (url?: string) => Promise<string>;
|
|
6
5
|
export declare const decompress: (toPath: string, tarPath: string) => Promise<void>;
|
|
7
6
|
export declare const seedDatabase: (instancePath: string, cloud: boolean) => Promise<void>;
|
|
8
|
-
export declare const createEnv: (
|
|
9
|
-
export declare const logFinale: (
|
|
7
|
+
export declare const createEnv: (installPath: string, databaseUrl: string) => Promise<void>;
|
|
8
|
+
export declare const logFinale: (installPath: string) => void;
|
|
10
9
|
export declare const isUrl: (string: string) => boolean;
|
|
@@ -9,11 +9,11 @@ import { got, RequestError } from 'got';
|
|
|
9
9
|
import inquirer from 'inquirer';
|
|
10
10
|
import * as semver from 'semver';
|
|
11
11
|
import tar from 'tar';
|
|
12
|
+
import { defaultPath } from '../../constants.js';
|
|
12
13
|
import { createPoolAndDatabaseIfNeeded } from '../../database.js';
|
|
13
14
|
import { packageJson } from '../../package-json.js';
|
|
14
15
|
import { cliConfig, ConfigKey, consoleLog, downloadFile, isTty, oraPromise, safeExecSync, } from '../../utils.js';
|
|
15
16
|
import { seedByPool } from '../database/seed/index.js';
|
|
16
|
-
export const defaultPath = path.join(os.homedir(), 'logto');
|
|
17
17
|
const pgRequired = new semver.SemVer('14.0.0');
|
|
18
18
|
export const validateNodeVersion = () => {
|
|
19
19
|
const required = [new semver.SemVer('18.12.0')];
|
|
@@ -29,7 +29,7 @@ export const validateNodeVersion = () => {
|
|
|
29
29
|
const validatePath = (value) => existsSync(path.resolve(value))
|
|
30
30
|
? `The path ${chalk.green(value)} already exists, please try another.`
|
|
31
31
|
: true;
|
|
32
|
-
export const
|
|
32
|
+
export const inquireInstallPath = async (initialPath) => {
|
|
33
33
|
if (!isTty()) {
|
|
34
34
|
assert(initialPath, new Error('Path is missing'));
|
|
35
35
|
return initialPath;
|
|
@@ -106,7 +106,6 @@ export const decompress = async (toPath, tarPath) => {
|
|
|
106
106
|
};
|
|
107
107
|
return oraPromise(run(), {
|
|
108
108
|
text: `Decompress to ${toPath}`,
|
|
109
|
-
prefixText: chalk.blue('[info]'),
|
|
110
109
|
}, true);
|
|
111
110
|
};
|
|
112
111
|
export const seedDatabase = async (instancePath, cloud) => {
|
|
@@ -119,19 +118,18 @@ export const seedDatabase = async (instancePath, cloud) => {
|
|
|
119
118
|
consoleLog.error(error);
|
|
120
119
|
await oraPromise(fs.rm(instancePath, { force: true, recursive: true }), {
|
|
121
120
|
text: 'Clean up',
|
|
122
|
-
prefixText: chalk.blue('[info]'),
|
|
123
121
|
});
|
|
124
122
|
consoleLog.fatal('Error occurred during seeding your Logto database. Nothing has changed since the seeding process was in a transaction.\n\n' +
|
|
125
123
|
` To skip the database seeding, append ${chalk.green('--skip-seed')} to the command options.`);
|
|
126
124
|
}
|
|
127
125
|
};
|
|
128
|
-
export const createEnv = async (
|
|
129
|
-
const dotEnvPath = path.resolve(
|
|
126
|
+
export const createEnv = async (installPath, databaseUrl) => {
|
|
127
|
+
const dotEnvPath = path.resolve(installPath, '.env');
|
|
130
128
|
await fs.writeFile(dotEnvPath, `DB_URL=${databaseUrl}`, 'utf8');
|
|
131
129
|
consoleLog.info(`Saved database URL to ${chalk.blue(dotEnvPath)}`);
|
|
132
130
|
};
|
|
133
|
-
export const logFinale = (
|
|
134
|
-
const startCommand = `cd ${
|
|
131
|
+
export const logFinale = (installPath) => {
|
|
132
|
+
const startCommand = `cd ${installPath} && npm start`;
|
|
135
133
|
consoleLog.info(`Use the command below to start Logto. Happy hacking!\n\n ${chalk.green(startCommand)}`);
|
|
136
134
|
};
|
|
137
135
|
export const isUrl = (string) => {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { isLanguageTag } from '@logto/language-kit';
|
|
2
2
|
import { isBuiltInLanguageTag as isPhrasesBuiltInLanguageTag } from '@logto/phrases';
|
|
3
|
-
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-
|
|
4
|
-
import { consoleLog } from '../../utils.js';
|
|
5
|
-
import { inquireInstancePath } from '../connector/utils.js';
|
|
3
|
+
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-experience';
|
|
4
|
+
import { consoleLog, inquireInstancePath } from '../../utils.js';
|
|
6
5
|
import { createFullTranslation } from './utils.js';
|
|
7
6
|
const create = {
|
|
8
7
|
command: ['create <language-tag>', 'c'],
|
|
@@ -26,11 +25,11 @@ const create = {
|
|
|
26
25
|
languageTag,
|
|
27
26
|
});
|
|
28
27
|
if (isPhrasesUiBuiltInLanguageTag(languageTag)) {
|
|
29
|
-
consoleLog.info(languageTag + ' is a built-in tag of phrases-
|
|
28
|
+
consoleLog.info(languageTag + ' is a built-in tag of phrases-experience, updating untranslated phrases');
|
|
30
29
|
}
|
|
31
30
|
await createFullTranslation({
|
|
32
31
|
instancePath,
|
|
33
|
-
packageName: 'phrases-
|
|
32
|
+
packageName: 'phrases-experience',
|
|
34
33
|
languageTag,
|
|
35
34
|
});
|
|
36
35
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { languages } from '@logto/language-kit';
|
|
2
2
|
import { isBuiltInLanguageTag as isPhrasesBuiltInLanguageTag } from '@logto/phrases';
|
|
3
|
-
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-
|
|
3
|
+
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-experience';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { consoleLog } from '../../utils.js';
|
|
6
6
|
const listTags = {
|
|
@@ -11,7 +11,7 @@ const listTags = {
|
|
|
11
11
|
consoleLog.plain(...[
|
|
12
12
|
tag,
|
|
13
13
|
isPhrasesBuiltInLanguageTag(tag) && chalk.blue('phrases'),
|
|
14
|
-
isPhrasesUiBuiltInLanguageTag(tag) && chalk.blue('phrases-
|
|
14
|
+
isPhrasesUiBuiltInLanguageTag(tag) && chalk.blue('phrases-experience'),
|
|
15
15
|
].filter(Boolean));
|
|
16
16
|
}
|
|
17
17
|
},
|
|
@@ -4,9 +4,10 @@ type GetTranslationPromptProperties = {
|
|
|
4
4
|
targetLanguage: LanguageTag;
|
|
5
5
|
extraPrompt?: string;
|
|
6
6
|
};
|
|
7
|
+
export declare const untranslatedMark = "/** UNTRANSLATED */";
|
|
7
8
|
/**
|
|
8
9
|
* Note:
|
|
9
|
-
* The input token limit of GPT 3.5 is 2048, the following prompt tokens with sourceFileContent is about
|
|
10
|
+
* The input token limit of GPT 3.5 is 2048, the following prompt tokens with sourceFileContent is about 1600.
|
|
10
11
|
* Remember to check the token limit before adding more prompt.
|
|
11
12
|
* Tokens can be counted in https://platform.openai.com/tokenizer
|
|
12
13
|
*/
|
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
import { languages } from '@logto/language-kit';
|
|
2
2
|
import { conditionalString } from '@silverhand/essentials';
|
|
3
|
+
export const untranslatedMark = '/** UNTRANSLATED */';
|
|
3
4
|
/**
|
|
4
5
|
* Note:
|
|
5
|
-
* The input token limit of GPT 3.5 is 2048, the following prompt tokens with sourceFileContent is about
|
|
6
|
+
* The input token limit of GPT 3.5 is 2048, the following prompt tokens with sourceFileContent is about 1600.
|
|
6
7
|
* Remember to check the token limit before adding more prompt.
|
|
7
8
|
* Tokens can be counted in https://platform.openai.com/tokenizer
|
|
8
9
|
*/
|
|
9
10
|
export const getTranslationPromptMessages = ({ sourceFileContent, targetLanguage, extraPrompt, }) => [
|
|
10
11
|
{
|
|
11
12
|
role: 'assistant',
|
|
12
|
-
content: `You are a
|
|
13
|
+
content: `You are a assistant translator and will receive a TypeScript object. Traverse and find object values with "${untranslatedMark}" annotation on the top, then translate these values to target locale "${languages[targetLanguage]}". Remove the "${untranslatedMark}" annotations from output. Escape the single quotes (if any) in translated results by prepending a backslash. Keep the interpolation double curly brackets and their inner values intact. Make sure there is a space between the CJK and non-CJK characters. Prefer using "你" instead of "您" in Chinese. Do not include sample code snippet below in the final output. ${conditionalString(extraPrompt)}
|
|
13
14
|
|
|
14
|
-
Take
|
|
15
|
+
Take translating to zh-CN as an example, if the input is:
|
|
15
16
|
\`\`\`ts
|
|
16
17
|
import others from './others.js';
|
|
17
18
|
|
|
18
19
|
const translation = {
|
|
19
|
-
hello: '
|
|
20
|
-
|
|
20
|
+
hello: 'Hello',
|
|
21
|
+
${untranslatedMark}
|
|
22
|
+
world: 'world',
|
|
21
23
|
others,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export default translation;
|
|
25
27
|
\`\`\`
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
Then the output should be:
|
|
27
30
|
\`\`\`ts
|
|
28
31
|
import others from './others.js';
|
|
29
32
|
|
|
30
33
|
const translation = {
|
|
31
|
-
hello: '
|
|
34
|
+
hello: 'Hello',
|
|
32
35
|
world: '世界',
|
|
33
36
|
others,
|
|
34
37
|
};
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
1
|
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
3
|
import { isLanguageTag } from '@logto/language-kit';
|
|
6
4
|
import ora from 'ora';
|
|
7
|
-
import { consoleLog } from '../../../utils.js';
|
|
8
|
-
import {
|
|
9
|
-
import { praseLocaleFiles, syncPhraseKeysAndFileStructure } from './utils.js';
|
|
10
|
-
const execPromise = promisify(execFile);
|
|
5
|
+
import { consoleLog, inquireInstancePath, lintLocaleFiles } from '../../../utils.js';
|
|
6
|
+
import { parseLocaleFiles, syncPhraseKeysAndFileStructure } from './utils.js';
|
|
11
7
|
const syncKeys = {
|
|
12
8
|
command: ['sync-keys', 'sk'],
|
|
13
9
|
describe: [
|
|
@@ -26,7 +22,7 @@ const syncKeys = {
|
|
|
26
22
|
.option('package', {
|
|
27
23
|
alias: 'pkg',
|
|
28
24
|
type: 'string',
|
|
29
|
-
describe: 'The package name of the phrases, one of `phrases` or `phrases-
|
|
25
|
+
describe: 'The package name of the phrases, one of `phrases` or `phrases-experience`',
|
|
30
26
|
default: 'phrases',
|
|
31
27
|
})
|
|
32
28
|
.option('target', {
|
|
@@ -50,14 +46,14 @@ const syncKeys = {
|
|
|
50
46
|
if (baselineTag === targetTag) {
|
|
51
47
|
consoleLog.fatal('Baseline and target cannot be the same');
|
|
52
48
|
}
|
|
53
|
-
if (packageName !== 'phrases' && packageName !== 'phrases-
|
|
54
|
-
consoleLog.fatal('Invalid package name, expected `phrases` or `phrases-
|
|
49
|
+
if (packageName !== 'phrases' && packageName !== 'phrases-experience') {
|
|
50
|
+
consoleLog.fatal('Invalid package name, expected `phrases` or `phrases-experience`');
|
|
55
51
|
}
|
|
56
52
|
const instancePath = await inquireInstancePath(inputPath);
|
|
57
53
|
const phrasesPath = path.join(instancePath, 'packages', packageName);
|
|
58
54
|
const localesPath = path.join(phrasesPath, 'src/locales');
|
|
59
55
|
const entrypoint = path.join(localesPath, baselineTag.toLowerCase(), 'index.ts');
|
|
60
|
-
const baseline =
|
|
56
|
+
const baseline = parseLocaleFiles(entrypoint);
|
|
61
57
|
const targetLocales = targetTag === 'all' ? fs.readdirSync(localesPath) : [targetTag.toLowerCase()];
|
|
62
58
|
/* eslint-disable no-await-in-loop */
|
|
63
59
|
for (const target of targetLocales) {
|
|
@@ -73,11 +69,7 @@ const syncKeys = {
|
|
|
73
69
|
}
|
|
74
70
|
/* eslint-enable no-await-in-loop */
|
|
75
71
|
if (!skipLint) {
|
|
76
|
-
|
|
77
|
-
text: 'Running `eslint --fix` for locales',
|
|
78
|
-
}).start();
|
|
79
|
-
await execPromise('pnpm', ['eslint', '--ext', '.ts', path.relative(phrasesPath, localesPath), '--fix'], { cwd: phrasesPath });
|
|
80
|
-
spinner.succeed('Ran `eslint --fix` for locales');
|
|
72
|
+
void lintLocaleFiles(instancePath, packageName);
|
|
81
73
|
}
|
|
82
74
|
},
|
|
83
75
|
};
|
|
@@ -5,7 +5,7 @@ type FileStructure = {
|
|
|
5
5
|
};
|
|
6
6
|
};
|
|
7
7
|
type NestedPhraseObject = {
|
|
8
|
-
[key: string]: string | NestedPhraseObject;
|
|
8
|
+
[key: string]: [phrase: string, isTranslated: boolean] | NestedPhraseObject;
|
|
9
9
|
};
|
|
10
10
|
type ParsedTuple = readonly [NestedPhraseObject, FileStructure];
|
|
11
11
|
/**
|
|
@@ -19,7 +19,8 @@ type ParsedTuple = readonly [NestedPhraseObject, FileStructure];
|
|
|
19
19
|
* import errors from './errors/index.js';
|
|
20
20
|
*
|
|
21
21
|
* const translation = {
|
|
22
|
-
*
|
|
22
|
+
* // UNTRANSLATED
|
|
23
|
+
* page_title: 'Applications',
|
|
23
24
|
* errors,
|
|
24
25
|
* };
|
|
25
26
|
* ```
|
|
@@ -28,9 +29,9 @@ type ParsedTuple = readonly [NestedPhraseObject, FileStructure];
|
|
|
28
29
|
*
|
|
29
30
|
* ```ts
|
|
30
31
|
* {
|
|
31
|
-
* page_title: 'Anwendungen',
|
|
32
|
+
* page_title: ['Anwendungen', false],
|
|
32
33
|
* errors: {
|
|
33
|
-
* page_not_found: 'Seite nicht gefunden',
|
|
34
|
+
* page_not_found: ['Seite nicht gefunden', true],
|
|
34
35
|
* },
|
|
35
36
|
* }
|
|
36
37
|
* ```
|
|
@@ -51,6 +52,6 @@ type ParsedTuple = readonly [NestedPhraseObject, FileStructure];
|
|
|
51
52
|
* @returns A tuple of the nested object of phrases and the file structure
|
|
52
53
|
*
|
|
53
54
|
*/
|
|
54
|
-
export declare const
|
|
55
|
+
export declare const parseLocaleFiles: (filePath: string) => ParsedTuple;
|
|
55
56
|
export declare const syncPhraseKeysAndFileStructure: (baseline: ParsedTuple, targetLocale: string, targetDirectory: string) => Promise<void>;
|
|
56
57
|
export {};
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { tryThat } from '@silverhand/essentials';
|
|
5
5
|
import ts from 'typescript';
|
|
6
6
|
import { consoleLog } from '../../../utils.js';
|
|
7
|
+
const untranslatedMark = 'UNTRANSLATED';
|
|
7
8
|
/**
|
|
8
9
|
* Given a entrypoint file path of a language, parse the nested object of
|
|
9
10
|
* phrases and the file structure.
|
|
@@ -15,7 +16,8 @@ import { consoleLog } from '../../../utils.js';
|
|
|
15
16
|
* import errors from './errors/index.js';
|
|
16
17
|
*
|
|
17
18
|
* const translation = {
|
|
18
|
-
*
|
|
19
|
+
* // UNTRANSLATED
|
|
20
|
+
* page_title: 'Applications',
|
|
19
21
|
* errors,
|
|
20
22
|
* };
|
|
21
23
|
* ```
|
|
@@ -24,9 +26,9 @@ import { consoleLog } from '../../../utils.js';
|
|
|
24
26
|
*
|
|
25
27
|
* ```ts
|
|
26
28
|
* {
|
|
27
|
-
* page_title: 'Anwendungen',
|
|
29
|
+
* page_title: ['Anwendungen', false],
|
|
28
30
|
* errors: {
|
|
29
|
-
* page_not_found: 'Seite nicht gefunden',
|
|
31
|
+
* page_not_found: ['Seite nicht gefunden', true],
|
|
30
32
|
* },
|
|
31
33
|
* }
|
|
32
34
|
* ```
|
|
@@ -47,7 +49,7 @@ import { consoleLog } from '../../../utils.js';
|
|
|
47
49
|
* @returns A tuple of the nested object of phrases and the file structure
|
|
48
50
|
*
|
|
49
51
|
*/
|
|
50
|
-
export const
|
|
52
|
+
export const parseLocaleFiles = (filePath) => {
|
|
51
53
|
const content = readFileSync(filePath, 'utf8');
|
|
52
54
|
const ast = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
53
55
|
const importIdentifierPath = new Map();
|
|
@@ -73,30 +75,47 @@ export const praseLocaleFiles = (filePath) => {
|
|
|
73
75
|
}
|
|
74
76
|
const resolvedPath = path.resolve(path.dirname(filePath), importPath);
|
|
75
77
|
// Recursively parse the nested object from the imported file
|
|
76
|
-
const [phrases, structure] =
|
|
77
|
-
|
|
78
|
+
const [phrases, structure] = parseLocaleFiles(resolvedPath);
|
|
79
|
+
/* eslint-disable @silverhand/fp/no-mutation */
|
|
78
80
|
nestedObject[key] = phrases;
|
|
79
|
-
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
80
81
|
fileStructure[key] = {
|
|
81
82
|
filePath: importPath,
|
|
82
83
|
structure,
|
|
83
84
|
};
|
|
85
|
+
/* eslint-enable @silverhand/fp/no-mutation */
|
|
84
86
|
}
|
|
85
87
|
if (ts.isPropertyAssignment(property)) {
|
|
86
88
|
const key = property.name.getText();
|
|
87
89
|
// Nested object, recursively parse it
|
|
88
90
|
if (ts.isObjectLiteralExpression(property.initializer)) {
|
|
89
91
|
const [phrases, structure] = traverseNode(property.initializer, {}, {});
|
|
90
|
-
|
|
92
|
+
/* eslint-disable @silverhand/fp/no-mutation */
|
|
91
93
|
nestedObject[key] = phrases;
|
|
92
|
-
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
93
94
|
fileStructure[key] = { structure };
|
|
95
|
+
/* eslint-enable @silverhand/fp/no-mutation */
|
|
94
96
|
}
|
|
95
97
|
else if (ts.isStringLiteral(property.initializer) ||
|
|
96
98
|
ts.isNoSubstitutionTemplateLiteral(property.initializer)) {
|
|
97
99
|
const value = property.initializer.getText();
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
const commentRanges = ts.getLeadingCommentRanges(property.getFullText(), 0);
|
|
101
|
+
if (commentRanges?.[0]) {
|
|
102
|
+
if (commentRanges.length > 1) {
|
|
103
|
+
consoleLog.fatal('Multiple comments found for property:', property);
|
|
104
|
+
}
|
|
105
|
+
const commentRange = commentRanges[0];
|
|
106
|
+
const comment = property.getFullText().slice(commentRange.pos, commentRange.end);
|
|
107
|
+
if (comment.includes(untranslatedMark)) {
|
|
108
|
+
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
109
|
+
nestedObject[key] = [value, false];
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
consoleLog.fatal('Unsupported comment:', comment);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
117
|
+
nestedObject[key] = [value, true];
|
|
118
|
+
}
|
|
100
119
|
}
|
|
101
120
|
else {
|
|
102
121
|
consoleLog.fatal('Unsupported property:', property);
|
|
@@ -193,14 +212,19 @@ const traverseNode = async (baseline, targetObject, targetFilePath, isRoot = fal
|
|
|
193
212
|
const [baselineObject, baselineStructure] = baseline;
|
|
194
213
|
for (const [key, value] of Object.entries(baselineObject)) {
|
|
195
214
|
const existingValue = targetObject[key];
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
// the
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
if (Array.isArray(value)) {
|
|
216
|
+
const [phrase] = value;
|
|
217
|
+
// If the key exists in the target language and the value is a translated
|
|
218
|
+
// string, use the value of the target language; otherwise, use the value
|
|
219
|
+
// of the baseline language and add a comment to indicate that the phrase
|
|
220
|
+
// is untranslated to help identify missing translations.
|
|
221
|
+
if (Array.isArray(existingValue) && existingValue[1]) {
|
|
222
|
+
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${key}: ${existingValue[0]},\n`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}/** ${untranslatedMark} */\n`);
|
|
226
|
+
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${key}: ${phrase},\n`);
|
|
227
|
+
}
|
|
204
228
|
}
|
|
205
229
|
// Not a string, treat it as a nested object or an import
|
|
206
230
|
else {
|
|
@@ -208,12 +232,12 @@ const traverseNode = async (baseline, targetObject, targetFilePath, isRoot = fal
|
|
|
208
232
|
// If the key has a file structure, treat it as an import
|
|
209
233
|
if (keyStructure?.filePath) {
|
|
210
234
|
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${key},\n`);
|
|
211
|
-
await traverseNode([value, keyStructure.structure],
|
|
235
|
+
await traverseNode([value, keyStructure.structure], Array.isArray(existingValue) ? {} : existingValue ?? {}, path.join(targetDirectory, keyStructure.filePath));
|
|
212
236
|
}
|
|
213
237
|
// Otherwise, treat it as a nested object.
|
|
214
238
|
else {
|
|
215
239
|
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${key}: {\n`);
|
|
216
|
-
await traverseObject([value, keyStructure?.structure ?? {}],
|
|
240
|
+
await traverseObject([value, keyStructure?.structure ?? {}], Array.isArray(existingValue) ? {} : existingValue ?? {}, tabSize + 2);
|
|
217
241
|
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}},\n`);
|
|
218
242
|
}
|
|
219
243
|
}
|
|
@@ -229,7 +253,7 @@ const traverseNode = async (baseline, targetObject, targetFilePath, isRoot = fal
|
|
|
229
253
|
export const syncPhraseKeysAndFileStructure = async (baseline, targetLocale, targetDirectory) => {
|
|
230
254
|
const targetEntrypoint = path.join(targetDirectory, 'index.ts');
|
|
231
255
|
const isTargetLocaleExist = existsSync(targetEntrypoint);
|
|
232
|
-
const targetObject = isTargetLocaleExist ?
|
|
256
|
+
const targetObject = isTargetLocaleExist ? parseLocaleFiles(targetEntrypoint)[0] : {};
|
|
233
257
|
const backupDirectory = targetDirectory + '.bak';
|
|
234
258
|
if (isTargetLocaleExist) {
|
|
235
259
|
await fs.rename(targetDirectory, backupDirectory);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { languages } from '@logto/language-kit';
|
|
2
2
|
import { isBuiltInLanguageTag as isPhrasesBuiltInLanguageTag } from '@logto/phrases';
|
|
3
|
-
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-
|
|
3
|
+
import { isBuiltInLanguageTag as isPhrasesUiBuiltInLanguageTag } from '@logto/phrases-experience';
|
|
4
4
|
import PQueue from 'p-queue';
|
|
5
|
-
import { inquireInstancePath } from '
|
|
5
|
+
import { inquireInstancePath, lintLocaleFiles } from '../../utils.js';
|
|
6
6
|
import { baseLanguage, syncTranslation } from './utils.js';
|
|
7
7
|
const sync = {
|
|
8
8
|
command: ['sync'],
|
|
@@ -19,22 +19,25 @@ const sync = {
|
|
|
19
19
|
verbose: false,
|
|
20
20
|
queue,
|
|
21
21
|
};
|
|
22
|
+
/* eslint-disable no-await-in-loop */
|
|
22
23
|
if (isPhrasesBuiltInLanguageTag(languageTag)) {
|
|
23
|
-
|
|
24
|
+
await syncTranslation({
|
|
24
25
|
...baseOptions,
|
|
25
26
|
packageName: 'phrases',
|
|
26
27
|
languageTag,
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
30
|
if (isPhrasesUiBuiltInLanguageTag(languageTag)) {
|
|
30
|
-
|
|
31
|
+
await syncTranslation({
|
|
31
32
|
...baseOptions,
|
|
32
|
-
packageName: 'phrases-
|
|
33
|
+
packageName: 'phrases-experience',
|
|
33
34
|
languageTag,
|
|
34
35
|
});
|
|
35
36
|
}
|
|
37
|
+
/* eslint-enable no-await-in-loop */
|
|
36
38
|
}
|
|
37
39
|
await queue.onIdle();
|
|
40
|
+
void lintLocaleFiles(instancePath);
|
|
38
41
|
},
|
|
39
42
|
};
|
|
40
43
|
export default sync;
|
|
@@ -5,7 +5,7 @@ export declare const readLocaleFiles: (directory: string) => Promise<string[]>;
|
|
|
5
5
|
export declare const readBaseLocaleFiles: (directory: string) => Promise<string[]>;
|
|
6
6
|
export type TranslationOptions = {
|
|
7
7
|
instancePath: string;
|
|
8
|
-
packageName: 'phrases' | 'phrases-
|
|
8
|
+
packageName: 'phrases' | 'phrases-experience';
|
|
9
9
|
languageTag: LanguageTag;
|
|
10
10
|
verbose?: boolean;
|
|
11
11
|
queue?: PQueue;
|
|
@@ -5,8 +5,8 @@ import { conditionalString } from '@silverhand/essentials';
|
|
|
5
5
|
import PQueue from 'p-queue';
|
|
6
6
|
import { consoleLog } from '../../utils.js';
|
|
7
7
|
import { createOpenaiApi, translate } from './openai.js';
|
|
8
|
+
import { untranslatedMark } from './prompts.js';
|
|
8
9
|
export const baseLanguage = 'en';
|
|
9
|
-
const untranslatedMark = '// UNTRANSLATED';
|
|
10
10
|
export const readLocaleFiles = async (directory) => {
|
|
11
11
|
const entities = await fs.readdir(directory, { withFileTypes: true });
|
|
12
12
|
const result = await Promise.all(entities.map(async (entity) => {
|
|
@@ -91,7 +91,6 @@ export const syncTranslation = async ({ instancePath, packageName, languageTag,
|
|
|
91
91
|
api: openai,
|
|
92
92
|
sourceFilePath: targetLocaleFile,
|
|
93
93
|
targetLanguage: languageTag,
|
|
94
|
-
extraPrompt: `Object values without an "${untranslatedMark}" mark should be skipped and keep its original value. Remember to remove the "${untranslatedMark}" mark with the spaces before and after it in the output content.`,
|
|
95
94
|
});
|
|
96
95
|
if (!result) {
|
|
97
96
|
consoleLog.fatal(`Unable to translate ${targetLocaleFile}`);
|
package/lib/constants.d.ts
CHANGED
package/lib/constants.js
CHANGED
package/lib/package-json.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare const packageJson: {
|
|
|
44
44
|
"@logto/core-kit": string;
|
|
45
45
|
"@logto/language-kit": string;
|
|
46
46
|
"@logto/phrases": string;
|
|
47
|
-
"@logto/phrases-
|
|
47
|
+
"@logto/phrases-experience": string;
|
|
48
48
|
"@logto/schemas": string;
|
|
49
49
|
"@logto/shared": string;
|
|
50
50
|
"@silverhand/essentials": string;
|
package/lib/package-json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const packageJson = {
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -44,13 +44,13 @@ export const packageJson = {
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@logto/connector-kit": "workspace:^1.1.1",
|
|
47
|
-
"@logto/core-kit": "workspace:^2.
|
|
47
|
+
"@logto/core-kit": "workspace:^2.1.1",
|
|
48
48
|
"@logto/language-kit": "workspace:^1.0.0",
|
|
49
|
-
"@logto/phrases": "workspace:^1.
|
|
50
|
-
"@logto/phrases-
|
|
51
|
-
"@logto/schemas": "workspace:1.
|
|
52
|
-
"@logto/shared": "workspace:^2.0.
|
|
53
|
-
"@silverhand/essentials": "^2.
|
|
49
|
+
"@logto/phrases": "workspace:^1.5.0",
|
|
50
|
+
"@logto/phrases-experience": "workspace:^1.3.1",
|
|
51
|
+
"@logto/schemas": "workspace:1.9.1",
|
|
52
|
+
"@logto/shared": "workspace:^2.0.1",
|
|
53
|
+
"@silverhand/essentials": "^2.8.4",
|
|
54
54
|
"chalk": "^5.0.0",
|
|
55
55
|
"decamelize": "^6.0.0",
|
|
56
56
|
"dotenv": "^16.0.0",
|
|
@@ -87,7 +87,7 @@ export const packageJson = {
|
|
|
87
87
|
"jest": "^29.5.0",
|
|
88
88
|
"lint-staged": "^14.0.0",
|
|
89
89
|
"prettier": "^3.0.0",
|
|
90
|
-
"sinon": "^
|
|
90
|
+
"sinon": "^16.0.0"
|
|
91
91
|
},
|
|
92
92
|
"eslintConfig": {
|
|
93
93
|
"extends": "@silverhand",
|
package/lib/utils.d.ts
CHANGED
|
@@ -28,8 +28,10 @@ export declare const getCliConfigWithPrompt: ({ key, readableKey, comments, defa
|
|
|
28
28
|
* findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
|
|
29
29
|
*/
|
|
30
30
|
export declare function findLastIndex<T>(array: readonly T[], predicate: (value: T, index: number, object: readonly T[]) => boolean): number;
|
|
31
|
+
export declare const inquireInstancePath: (initialPath?: string) => Promise<string>;
|
|
31
32
|
export type ConnectorPackage = {
|
|
32
33
|
name: string;
|
|
33
34
|
path: string;
|
|
34
35
|
};
|
|
35
36
|
export declare const getConnectorPackagesFromDirectory: (directory: string) => Promise<ConnectorPackage[]>;
|
|
37
|
+
export declare const lintLocaleFiles: (instancePath: string, packageName?: 'phrases' | 'phrases-experience') => Promise<void>;
|
package/lib/utils.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
1
|
+
import { execSync, execFile } from 'node:child_process';
|
|
2
2
|
import { createWriteStream, existsSync } from 'node:fs';
|
|
3
3
|
import { readdir, readFile } from 'node:fs/promises';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
6
7
|
import { ConsoleLog } from '@logto/shared';
|
|
7
|
-
import { conditionalString } from '@silverhand/essentials';
|
|
8
|
+
import { assert, conditionalString } from '@silverhand/essentials';
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
import { got } from 'got';
|
|
10
11
|
import { HttpsProxyAgent } from 'hpagent';
|
|
11
12
|
import inquirer from 'inquirer';
|
|
12
13
|
import ora from 'ora';
|
|
13
14
|
import { z } from 'zod';
|
|
15
|
+
import { coreDirectory, defaultPath } from './constants.js';
|
|
14
16
|
export const safeExecSync = (command) => {
|
|
15
17
|
try {
|
|
16
18
|
return execSync(command, { encoding: 'utf8', stdio: 'pipe' });
|
|
@@ -40,7 +42,7 @@ export const downloadFile = async (url, destination) => {
|
|
|
40
42
|
});
|
|
41
43
|
const spinner = ora({
|
|
42
44
|
text: 'Connecting',
|
|
43
|
-
prefixText:
|
|
45
|
+
prefixText: ConsoleLog.prefixes.info,
|
|
44
46
|
}).start();
|
|
45
47
|
stream.pipe(file);
|
|
46
48
|
return new Promise((resolve, reject) => {
|
|
@@ -66,7 +68,7 @@ export const getPathInModule = (moduleName, relativePath = '/') =>
|
|
|
66
68
|
// https://stackoverflow.com/a/49455609/12514940
|
|
67
69
|
path.join(path.dirname(createRequire(import.meta.url).resolve(`${moduleName}/package.json`)), relativePath);
|
|
68
70
|
export const oraPromise = async (promise, options, exitOnError = false) => {
|
|
69
|
-
const spinner = ora(options).start();
|
|
71
|
+
const spinner = ora({ prefixText: ConsoleLog.prefixes.info, ...options }).start();
|
|
70
72
|
try {
|
|
71
73
|
const result = await promise;
|
|
72
74
|
spinner.succeed();
|
|
@@ -120,6 +122,49 @@ export function findLastIndex(array, predicate) {
|
|
|
120
122
|
}
|
|
121
123
|
return -1;
|
|
122
124
|
}
|
|
125
|
+
const buildPathErrorMessage = (value) => `The path ${chalk.green(value)} does not contain a Logto instance. Please try another.`;
|
|
126
|
+
const validatePath = async (value) => {
|
|
127
|
+
const corePackageJsonPath = path.resolve(path.join(value, coreDirectory, 'package.json'));
|
|
128
|
+
if (!existsSync(corePackageJsonPath)) {
|
|
129
|
+
return buildPathErrorMessage(value);
|
|
130
|
+
}
|
|
131
|
+
const packageJson = await readFile(corePackageJsonPath, { encoding: 'utf8' });
|
|
132
|
+
const packageName = await z
|
|
133
|
+
.object({ name: z.string() })
|
|
134
|
+
.parseAsync(JSON.parse(packageJson))
|
|
135
|
+
.then(({ name }) => name)
|
|
136
|
+
.catch(() => '');
|
|
137
|
+
if (packageName !== '@logto/core') {
|
|
138
|
+
return buildPathErrorMessage(value);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
export const inquireInstancePath = async (initialPath) => {
|
|
143
|
+
const inquire = async () => {
|
|
144
|
+
if (!initialPath && (await validatePath('.')) === true) {
|
|
145
|
+
return path.resolve('.');
|
|
146
|
+
}
|
|
147
|
+
if (!isTty()) {
|
|
148
|
+
assert(initialPath, new Error('Path is missing'));
|
|
149
|
+
return initialPath;
|
|
150
|
+
}
|
|
151
|
+
const { instancePath } = await inquirer.prompt({
|
|
152
|
+
name: 'instancePath',
|
|
153
|
+
message: 'Where is your Logto instance?',
|
|
154
|
+
type: 'input',
|
|
155
|
+
default: defaultPath,
|
|
156
|
+
filter: (value) => value.trim(),
|
|
157
|
+
validate: validatePath,
|
|
158
|
+
}, { instancePath: initialPath });
|
|
159
|
+
return instancePath;
|
|
160
|
+
};
|
|
161
|
+
const instancePath = await inquire();
|
|
162
|
+
const validated = await validatePath(instancePath);
|
|
163
|
+
if (validated !== true) {
|
|
164
|
+
consoleLog.fatal(validated);
|
|
165
|
+
}
|
|
166
|
+
return instancePath;
|
|
167
|
+
};
|
|
123
168
|
const getConnectorPackageName = async (directory) => {
|
|
124
169
|
const filePath = path.join(directory, 'package.json');
|
|
125
170
|
if (!existsSync(filePath)) {
|
|
@@ -139,3 +184,20 @@ export const getConnectorPackagesFromDirectory = async (directory) => {
|
|
|
139
184
|
}));
|
|
140
185
|
return rawPackages.filter((packageInfo) => typeof packageInfo.name === 'string');
|
|
141
186
|
};
|
|
187
|
+
const execPromise = promisify(execFile);
|
|
188
|
+
export const lintLocaleFiles = async (
|
|
189
|
+
/** Logto instance path */
|
|
190
|
+
instancePath,
|
|
191
|
+
/** Target package name, ignore to lint both packages */
|
|
192
|
+
packageName) => {
|
|
193
|
+
const spinner = ora({
|
|
194
|
+
text: 'Running `eslint --fix` for locales',
|
|
195
|
+
}).start();
|
|
196
|
+
const targetPackages = packageName ? [packageName] : ['phrases', 'phrases-experience'];
|
|
197
|
+
await Promise.all(targetPackages.map(async (packageName) => {
|
|
198
|
+
const phrasesPath = path.join(instancePath, 'packages', packageName);
|
|
199
|
+
const localesPath = path.join(phrasesPath, 'src/locales');
|
|
200
|
+
await execPromise('pnpm', ['eslint', '--ext', '.ts', path.relative(phrasesPath, localesPath), '--fix'], { cwd: phrasesPath });
|
|
201
|
+
}));
|
|
202
|
+
spinner.succeed('Ran `eslint --fix` for locales');
|
|
203
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@logto/connector-kit": "^1.1.1",
|
|
32
|
-
"@logto/core-kit": "^2.
|
|
32
|
+
"@logto/core-kit": "^2.1.1",
|
|
33
33
|
"@logto/language-kit": "^1.0.0",
|
|
34
|
-
"@logto/phrases": "^1.
|
|
35
|
-
"@logto/phrases-
|
|
36
|
-
"@logto/schemas": "1.
|
|
37
|
-
"@logto/shared": "^2.0.
|
|
38
|
-
"@silverhand/essentials": "^2.
|
|
34
|
+
"@logto/phrases": "^1.5.0",
|
|
35
|
+
"@logto/phrases-experience": "^1.3.1",
|
|
36
|
+
"@logto/schemas": "1.9.1",
|
|
37
|
+
"@logto/shared": "^2.0.1",
|
|
38
|
+
"@silverhand/essentials": "^2.8.4",
|
|
39
39
|
"chalk": "^5.0.0",
|
|
40
40
|
"decamelize": "^6.0.0",
|
|
41
41
|
"dotenv": "^16.0.0",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"jest": "^29.5.0",
|
|
73
73
|
"lint-staged": "^14.0.0",
|
|
74
74
|
"prettier": "^3.0.0",
|
|
75
|
-
"sinon": "^
|
|
75
|
+
"sinon": "^16.0.0"
|
|
76
76
|
},
|
|
77
77
|
"eslintConfig": {
|
|
78
78
|
"extends": "@silverhand",
|