intor-cli 0.0.8 → 0.0.10
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/README.md +24 -8
- package/package.json +4 -4
- package/src/build/build-schemas/build-schemas.ts +2 -2
- package/src/build/build-types/utils/render-infer-node.ts +1 -1
- package/src/build/index.ts +1 -1
- package/src/build/types.ts +4 -3
- package/src/cli/commands/check.ts +9 -26
- package/src/cli/commands/generate.ts +2 -13
- package/src/cli/commands/utils/normalize-reader-options.ts +46 -0
- package/src/cli/commands/validate.ts +50 -0
- package/src/cli/index.ts +2 -0
- package/src/core/collect-messages/collect-other-locale-messages.ts +27 -0
- package/src/core/collect-messages/index.ts +1 -0
- package/src/core/constants/generated.ts +5 -0
- package/src/core/constants/index.ts +4 -3
- package/src/core/extract-usages/index.ts +1 -1
- package/src/core/generated/ensure-and-write.ts +11 -0
- package/src/core/generated/index.ts +6 -0
- package/src/core/generated/read-schema.ts +33 -0
- package/src/core/generated/write-messages-snapshot.ts +27 -0
- package/src/core/generated/write-schema.ts +9 -0
- package/src/core/generated/write-types.ts +8 -0
- package/src/core/index.ts +36 -9
- package/src/core/infer-schema/index.ts +2 -1
- package/src/core/infer-schema/infer-schemas.ts +5 -1
- package/src/core/infer-schema/messages/infer-messages-schema.ts +1 -1
- package/src/core/infer-schema/replacements/infer-replacements-schema.ts +1 -1
- package/src/core/infer-schema/rich/infer-rich-schema.ts +1 -1
- package/src/core/infer-schema/utils/infer-object.ts +8 -6
- package/src/core/infer-schema/utils/should-skip-key.ts +15 -0
- package/src/core/infer-schema/utils/strip-internal-keys.ts +30 -0
- package/src/features/check/check.ts +7 -15
- package/src/{core → features/check}/diagnostics/collect.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/enforce-missing-replacements.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/enforce-missing-rich.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/key/empty.ts +1 -1
- package/src/{core → features/check}/diagnostics/rules/key/not-found.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/pre-key/not-found.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/replacement/missing.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/replacement/not-allowed.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/replacement/unused.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/rich/missing.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/rich/not-allowed.ts +1 -2
- package/src/{core → features/check}/diagnostics/rules/rich/unused.ts +1 -2
- package/src/{core → features/check}/diagnostics/types.ts +1 -1
- package/src/{core → features/check}/diagnostics/utils/get-schema-node-at-path.ts +1 -1
- package/src/{core → features/check}/diagnostics/utils/index-usages-by-key.ts +1 -1
- package/src/features/check/print-summary.ts +15 -15
- package/src/features/generate/generate.ts +16 -8
- package/src/features/generate/print-configs.ts +3 -3
- package/src/features/generate/print-overrides.ts +44 -26
- package/src/features/generate/print-summary.ts +21 -13
- package/src/features/index.ts +1 -0
- package/src/features/print.ts +43 -0
- package/src/features/validate/index.ts +1 -0
- package/src/features/validate/messages/index.ts +1 -0
- package/src/features/validate/messages/validate-messages-schema.ts +36 -0
- package/src/features/validate/print-summary.ts +65 -0
- package/src/features/validate/replacements/index.ts +1 -0
- package/src/features/validate/replacements/validate-replacements-schema.ts +51 -0
- package/src/features/validate/rich/index.ts +1 -0
- package/src/features/validate/rich/validate-rich-schema.ts +48 -0
- package/src/features/validate/validate-locale-messages.ts +38 -0
- package/src/features/validate/validate.ts +84 -0
- package/src/core/constants/generated-files.ts +0 -3
- package/src/core/read-generated-schema.ts +0 -42
- package/src/core/write-generated-files.ts +0 -51
- package/src/features/print-title.ts +0 -7
- /package/src/{core → features/check}/diagnostics/group.ts +0 -0
- /package/src/{core → features/check}/diagnostics/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/messages.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/key/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/pre-key/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/replacement/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/rules/rich/index.ts +0 -0
- /package/src/{core → features/check}/diagnostics/utils/resolve-key-path.ts +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { InferNode } from "../../../core";
|
|
2
|
+
import type { ValidationResult } from "../validate-locale-messages";
|
|
3
|
+
import type { MessageObject } from "intor";
|
|
4
|
+
import { extractInterpolationNames } from "../../../core";
|
|
5
|
+
|
|
6
|
+
export function validateReplacementsSchema(
|
|
7
|
+
schema: InferNode,
|
|
8
|
+
messages: MessageObject,
|
|
9
|
+
path: string,
|
|
10
|
+
result: ValidationResult,
|
|
11
|
+
) {
|
|
12
|
+
// Only object schemas define required message keys
|
|
13
|
+
if (schema.kind !== "object") return;
|
|
14
|
+
|
|
15
|
+
// Traverse schema-defined keys and validate message presence
|
|
16
|
+
for (const key of Object.keys(schema.properties)) {
|
|
17
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
18
|
+
const expected = schema.properties[key];
|
|
19
|
+
const value = messages[key];
|
|
20
|
+
|
|
21
|
+
// Schema requires this key, but message does not provide it
|
|
22
|
+
if (value === undefined) continue;
|
|
23
|
+
|
|
24
|
+
// ------------------------------------------------------------
|
|
25
|
+
// Leaf string message: validate interpolation replacements
|
|
26
|
+
// ------------------------------------------------------------
|
|
27
|
+
if (typeof value === "string") {
|
|
28
|
+
if (expected.kind !== "object") continue;
|
|
29
|
+
|
|
30
|
+
const actualNames = extractInterpolationNames(value);
|
|
31
|
+
|
|
32
|
+
// Report any schema-required replacements missing in the message
|
|
33
|
+
for (const name of Object.keys(expected.properties)) {
|
|
34
|
+
if (actualNames.includes(name)) continue;
|
|
35
|
+
result.missingReplacements.push({ key: nextPath, name });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Recurse into nested message objects
|
|
42
|
+
if (typeof value === "object" && value !== null) {
|
|
43
|
+
validateReplacementsSchema(
|
|
44
|
+
expected,
|
|
45
|
+
value as MessageObject,
|
|
46
|
+
nextPath,
|
|
47
|
+
result,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { validateRichSchema } from "./validate-rich-schema";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { InferNode } from "../../../core";
|
|
2
|
+
import type { ValidationResult } from "../validate-locale-messages";
|
|
3
|
+
import { tokenize, type MessageObject, type Token } from "intor";
|
|
4
|
+
|
|
5
|
+
export function validateRichSchema(
|
|
6
|
+
schema: InferNode,
|
|
7
|
+
messages: MessageObject,
|
|
8
|
+
path: string,
|
|
9
|
+
result: ValidationResult,
|
|
10
|
+
) {
|
|
11
|
+
// Only object schemas define expected rich tags
|
|
12
|
+
if (schema.kind !== "object") return;
|
|
13
|
+
|
|
14
|
+
// Traverse schema-defined keys
|
|
15
|
+
for (const key of Object.keys(schema.properties)) {
|
|
16
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
17
|
+
const expected = schema.properties[key];
|
|
18
|
+
const value = messages[key];
|
|
19
|
+
|
|
20
|
+
// Schema requires this key, but message does not provide it
|
|
21
|
+
if (value === undefined) continue;
|
|
22
|
+
|
|
23
|
+
// --------------------------------------------------
|
|
24
|
+
// Leaf string message: validate rich tags
|
|
25
|
+
// --------------------------------------------------
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
if (expected.kind !== "object") continue;
|
|
28
|
+
|
|
29
|
+
const tokens: Token[] = tokenize(value);
|
|
30
|
+
const actualTags = new Set(
|
|
31
|
+
tokens.filter((t) => t.type === "tag-open").map((t) => t.name),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Report any schema-required rich tags missing in the message
|
|
35
|
+
for (const tag of Object.keys(expected.properties)) {
|
|
36
|
+
if (actualTags.has(tag)) continue;
|
|
37
|
+
result.missingRichTags.push({ key: nextPath, tag });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Recurse into nested message objects
|
|
44
|
+
if (typeof value === "object" && value !== null) {
|
|
45
|
+
validateRichSchema(expected, value as MessageObject, nextPath, result);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { InferredSchemas } from "../../core";
|
|
2
|
+
import type { MessageObject } from "intor";
|
|
3
|
+
import { validateMessagesSchema } from "./messages";
|
|
4
|
+
import { validateReplacementsSchema } from "./replacements";
|
|
5
|
+
import { validateRichSchema } from "./rich";
|
|
6
|
+
|
|
7
|
+
export interface ValidationResult {
|
|
8
|
+
missingKeys: string[];
|
|
9
|
+
missingReplacements: Array<{ key: string; name: string }>;
|
|
10
|
+
missingRichTags: Array<{ key: string; tag: string }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate a locale's messages against inferred semantic schemas.
|
|
15
|
+
*/
|
|
16
|
+
export function validateLocaleMessages(
|
|
17
|
+
schemas: InferredSchemas,
|
|
18
|
+
localeMessages: MessageObject,
|
|
19
|
+
): ValidationResult {
|
|
20
|
+
const result: ValidationResult = {
|
|
21
|
+
missingKeys: [],
|
|
22
|
+
missingReplacements: [],
|
|
23
|
+
missingRichTags: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
validateMessagesSchema(schemas.messagesSchema, localeMessages, "", result);
|
|
27
|
+
|
|
28
|
+
validateReplacementsSchema(
|
|
29
|
+
schemas.replacementsSchema,
|
|
30
|
+
localeMessages,
|
|
31
|
+
"",
|
|
32
|
+
result,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
validateRichSchema(schemas.richSchema, localeMessages, "", result);
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-process-exit */
|
|
2
|
+
import { discoverConfigs, readSchema, type ExtraExt } from "../../core";
|
|
3
|
+
import { collectOtherLocaleMessages } from "../../core";
|
|
4
|
+
import { printMissingSchema, printTitle } from "../print";
|
|
5
|
+
import { spinner } from "../spinner";
|
|
6
|
+
import { printSummary } from "./print-summary";
|
|
7
|
+
import {
|
|
8
|
+
validateLocaleMessages,
|
|
9
|
+
type ValidationResult,
|
|
10
|
+
} from "./validate-locale-messages";
|
|
11
|
+
|
|
12
|
+
export interface ValidateOptions {
|
|
13
|
+
exts?: Array<ExtraExt>;
|
|
14
|
+
customReaders?: Record<string, string>;
|
|
15
|
+
debug?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function validate({
|
|
19
|
+
exts = [],
|
|
20
|
+
customReaders,
|
|
21
|
+
debug,
|
|
22
|
+
}: ValidateOptions) {
|
|
23
|
+
printTitle("Validating intor translations");
|
|
24
|
+
spinner.start();
|
|
25
|
+
|
|
26
|
+
// -----------------------------------------------------------------------
|
|
27
|
+
// Discover configs from the current workspace
|
|
28
|
+
// -----------------------------------------------------------------------
|
|
29
|
+
const configEntries = await discoverConfigs(debug);
|
|
30
|
+
if (configEntries.length === 0) {
|
|
31
|
+
spinner.stop();
|
|
32
|
+
throw new Error("No Intor config found.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// -----------------------------------------------------------------------
|
|
37
|
+
// Read generated schema
|
|
38
|
+
// -----------------------------------------------------------------------
|
|
39
|
+
const schema = await readSchema();
|
|
40
|
+
|
|
41
|
+
const resultsByConfig: Record<
|
|
42
|
+
string,
|
|
43
|
+
Record<string, ValidationResult>
|
|
44
|
+
> = {};
|
|
45
|
+
|
|
46
|
+
// Per-config processing
|
|
47
|
+
for (const { config } of configEntries) {
|
|
48
|
+
const schemaConfig = schema.configs.find((c) => c.id === config.id);
|
|
49
|
+
if (!schemaConfig) {
|
|
50
|
+
spinner.stop();
|
|
51
|
+
printMissingSchema(config.id);
|
|
52
|
+
spinner.start();
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Load all non-default locale messages
|
|
57
|
+
const localeMessages = await collectOtherLocaleMessages(
|
|
58
|
+
config,
|
|
59
|
+
exts,
|
|
60
|
+
customReaders,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const results: Record<string, ValidationResult> = {};
|
|
64
|
+
for (const locale of config.supportedLocales) {
|
|
65
|
+
if (locale === config.defaultLocale) continue;
|
|
66
|
+
const messages = localeMessages[locale];
|
|
67
|
+
if (!messages) continue;
|
|
68
|
+
results[locale] = validateLocaleMessages(
|
|
69
|
+
schemaConfig.schemas,
|
|
70
|
+
messages,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
resultsByConfig[config.id] = results;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
spinner.stop();
|
|
78
|
+
printSummary(resultsByConfig);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
spinner.stop();
|
|
81
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { GeneratedSchema } from "../build";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE } from "./constants";
|
|
5
|
-
|
|
6
|
-
export interface ReadGeneratedSchemaOptions {
|
|
7
|
-
cwd?: string;
|
|
8
|
-
outDir?: string;
|
|
9
|
-
fileName?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function readGeneratedSchema(
|
|
13
|
-
options: ReadGeneratedSchemaOptions = {},
|
|
14
|
-
): Promise<GeneratedSchema> {
|
|
15
|
-
const {
|
|
16
|
-
cwd = process.cwd(),
|
|
17
|
-
outDir = DEFAULT_OUT_DIR,
|
|
18
|
-
fileName = DEFAULT_SCHEMA_FILE,
|
|
19
|
-
} = options;
|
|
20
|
-
|
|
21
|
-
const schemaPath = path.resolve(cwd, outDir, fileName);
|
|
22
|
-
|
|
23
|
-
let raw: string;
|
|
24
|
-
|
|
25
|
-
// Try to read
|
|
26
|
-
try {
|
|
27
|
-
raw = await fs.readFile(schemaPath, "utf8");
|
|
28
|
-
} catch {
|
|
29
|
-
throw new Error(
|
|
30
|
-
`Failed to read intor schema file at "${schemaPath}".\n Have you run "intor generate"?`,
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Try to parse
|
|
35
|
-
try {
|
|
36
|
-
return JSON.parse(raw) as GeneratedSchema;
|
|
37
|
-
} catch {
|
|
38
|
-
throw new Error(
|
|
39
|
-
`Invalid JSON format in intor schema file at "${schemaPath}".`,
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
DEFAULT_OUT_DIR,
|
|
5
|
-
DEFAULT_TYPES_FILE,
|
|
6
|
-
DEFAULT_SCHEMA_FILE,
|
|
7
|
-
} from "./constants";
|
|
8
|
-
|
|
9
|
-
export interface WriteOptions {
|
|
10
|
-
cwd?: string;
|
|
11
|
-
outDir?: string;
|
|
12
|
-
typesFileName?: string;
|
|
13
|
-
schemaFileName?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface WriteGeneratedFilesResult {
|
|
17
|
-
outDir: string;
|
|
18
|
-
typesPath: string;
|
|
19
|
-
schemaPath: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function writeGeneratedFiles(
|
|
23
|
-
{ types, schema }: { types: string; schema: unknown },
|
|
24
|
-
options: WriteOptions = {},
|
|
25
|
-
): Promise<WriteGeneratedFilesResult> {
|
|
26
|
-
const {
|
|
27
|
-
cwd = process.cwd(),
|
|
28
|
-
outDir = DEFAULT_OUT_DIR,
|
|
29
|
-
typesFileName = DEFAULT_TYPES_FILE,
|
|
30
|
-
schemaFileName = DEFAULT_SCHEMA_FILE,
|
|
31
|
-
} = options;
|
|
32
|
-
|
|
33
|
-
const resolvedOutDir = path.resolve(cwd, outDir);
|
|
34
|
-
const typesPath = path.join(resolvedOutDir, typesFileName);
|
|
35
|
-
const schemaPath = path.join(resolvedOutDir, schemaFileName);
|
|
36
|
-
|
|
37
|
-
// Ensure .intor directory exists
|
|
38
|
-
await fs.mkdir(resolvedOutDir, { recursive: true });
|
|
39
|
-
|
|
40
|
-
// Write types (as-is)
|
|
41
|
-
await fs.writeFile(typesPath, types, "utf8");
|
|
42
|
-
|
|
43
|
-
// Write schema (pretty JSON for diff / debug)
|
|
44
|
-
await fs.writeFile(schemaPath, JSON.stringify(schema, null, 2), "utf8");
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
outDir: resolvedOutDir,
|
|
48
|
-
typesPath,
|
|
49
|
-
schemaPath,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|