intor-cli 0.0.9 → 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.
Files changed (76) hide show
  1. package/README.md +24 -8
  2. package/package.json +4 -4
  3. package/src/build/build-schemas/build-schemas.ts +2 -2
  4. package/src/build/build-types/utils/render-infer-node.ts +1 -1
  5. package/src/build/index.ts +1 -1
  6. package/src/build/types.ts +4 -3
  7. package/src/cli/commands/check.ts +9 -26
  8. package/src/cli/commands/generate.ts +2 -13
  9. package/src/cli/commands/utils/normalize-reader-options.ts +46 -0
  10. package/src/cli/commands/validate.ts +50 -0
  11. package/src/cli/index.ts +2 -0
  12. package/src/core/collect-messages/collect-other-locale-messages.ts +27 -0
  13. package/src/core/collect-messages/index.ts +1 -0
  14. package/src/core/constants/generated.ts +5 -0
  15. package/src/core/constants/index.ts +4 -3
  16. package/src/core/extract-usages/index.ts +1 -1
  17. package/src/core/generated/ensure-and-write.ts +11 -0
  18. package/src/core/generated/index.ts +6 -0
  19. package/src/core/generated/read-schema.ts +33 -0
  20. package/src/core/generated/write-messages-snapshot.ts +27 -0
  21. package/src/core/generated/write-schema.ts +9 -0
  22. package/src/core/generated/write-types.ts +8 -0
  23. package/src/core/index.ts +36 -9
  24. package/src/core/infer-schema/index.ts +2 -1
  25. package/src/core/infer-schema/infer-schemas.ts +5 -1
  26. package/src/core/infer-schema/messages/infer-messages-schema.ts +1 -1
  27. package/src/core/infer-schema/replacements/infer-replacements-schema.ts +1 -1
  28. package/src/core/infer-schema/rich/infer-rich-schema.ts +1 -1
  29. package/src/core/infer-schema/utils/infer-object.ts +8 -6
  30. package/src/core/infer-schema/utils/should-skip-key.ts +15 -0
  31. package/src/core/infer-schema/utils/strip-internal-keys.ts +30 -0
  32. package/src/features/check/check.ts +7 -15
  33. package/src/{core → features/check}/diagnostics/collect.ts +1 -2
  34. package/src/{core → features/check}/diagnostics/rules/enforce-missing-replacements.ts +1 -2
  35. package/src/{core → features/check}/diagnostics/rules/enforce-missing-rich.ts +1 -2
  36. package/src/{core → features/check}/diagnostics/rules/key/empty.ts +1 -1
  37. package/src/{core → features/check}/diagnostics/rules/key/not-found.ts +1 -2
  38. package/src/{core → features/check}/diagnostics/rules/pre-key/not-found.ts +1 -2
  39. package/src/{core → features/check}/diagnostics/rules/replacement/missing.ts +1 -2
  40. package/src/{core → features/check}/diagnostics/rules/replacement/not-allowed.ts +1 -2
  41. package/src/{core → features/check}/diagnostics/rules/replacement/unused.ts +1 -2
  42. package/src/{core → features/check}/diagnostics/rules/rich/missing.ts +1 -2
  43. package/src/{core → features/check}/diagnostics/rules/rich/not-allowed.ts +1 -2
  44. package/src/{core → features/check}/diagnostics/rules/rich/unused.ts +1 -2
  45. package/src/{core → features/check}/diagnostics/types.ts +1 -1
  46. package/src/{core → features/check}/diagnostics/utils/get-schema-node-at-path.ts +1 -1
  47. package/src/{core → features/check}/diagnostics/utils/index-usages-by-key.ts +1 -1
  48. package/src/features/check/print-summary.ts +15 -15
  49. package/src/features/generate/generate.ts +16 -8
  50. package/src/features/generate/print-configs.ts +3 -3
  51. package/src/features/generate/print-overrides.ts +44 -26
  52. package/src/features/generate/print-summary.ts +21 -13
  53. package/src/features/index.ts +1 -0
  54. package/src/features/print.ts +43 -0
  55. package/src/features/validate/index.ts +1 -0
  56. package/src/features/validate/messages/index.ts +1 -0
  57. package/src/features/validate/messages/validate-messages-schema.ts +36 -0
  58. package/src/features/validate/print-summary.ts +65 -0
  59. package/src/features/validate/replacements/index.ts +1 -0
  60. package/src/features/validate/replacements/validate-replacements-schema.ts +51 -0
  61. package/src/features/validate/rich/index.ts +1 -0
  62. package/src/features/validate/rich/validate-rich-schema.ts +48 -0
  63. package/src/features/validate/validate-locale-messages.ts +38 -0
  64. package/src/features/validate/validate.ts +84 -0
  65. package/src/core/constants/generated-files.ts +0 -3
  66. package/src/core/read-generated-schema.ts +0 -42
  67. package/src/core/write-generated-files.ts +0 -51
  68. package/src/features/print-title.ts +0 -7
  69. /package/src/{core → features/check}/diagnostics/group.ts +0 -0
  70. /package/src/{core → features/check}/diagnostics/index.ts +0 -0
  71. /package/src/{core → features/check}/diagnostics/messages.ts +0 -0
  72. /package/src/{core → features/check}/diagnostics/rules/key/index.ts +0 -0
  73. /package/src/{core → features/check}/diagnostics/rules/pre-key/index.ts +0 -0
  74. /package/src/{core → features/check}/diagnostics/rules/replacement/index.ts +0 -0
  75. /package/src/{core → features/check}/diagnostics/rules/rich/index.ts +0 -0
  76. /package/src/{core → features/check}/diagnostics/utils/resolve-key-path.ts +0 -0
package/README.md CHANGED
@@ -24,7 +24,13 @@ CLI tool for intor.
24
24
  </table>
25
25
  </div>
26
26
 
27
- ## Usage
27
+ ## Overview
28
+
29
+ - **generate** — message schema & types
30
+ - **check** — usage analysis
31
+ - **validate** — locale completeness
32
+
33
+ ## Commands
28
34
 
29
35
  #### generate
30
36
 
@@ -33,8 +39,8 @@ npx intor-cli generate
33
39
  ```
34
40
 
35
41
  - Generates TypeScript types and schema artifacts
36
- - Designed to be safe, deterministic, and non-intrusive
37
- - Displays runtime message override details during generation
42
+ - Uses the default locale as the single source of truth
43
+ - Reports message override behavior during generation
38
44
 
39
45
  #### check
40
46
 
@@ -42,13 +48,23 @@ npx intor-cli generate
42
48
  npx intor-cli check
43
49
  ```
44
50
 
45
- - Statically extracts translator usages from your codebase
46
- - Validates preKey, message keys, replacements, and rich tags
51
+ - Statistically analyzes translator usage in your codebase
52
+ - Detects incorrect keys, replacements, and rich tag usage
47
53
  - Reports diagnostics with precise source locations
48
54
 
55
+ #### validate
56
+
57
+ ```bash
58
+ npx intor-cli validate
59
+ ```
60
+
61
+ - Validates locale message completeness against schemas
62
+ - Checks missing keys, replacements, and rich tags
63
+ - Reports issues grouped by config and locale
64
+
49
65
  ## Design Guarantees
50
66
 
51
67
  - Message types are inferred from the **_default locale_** only.
52
- - All locales are assumed to share the same message shape.
53
- - Locale is treated as a runtime dimension, not a structural one.
54
- - Generated types are intentionally conservative and do not validate locale completeness.
68
+ - All locales are expected to share the same message shape.
69
+ - Locale is treated strictly as a runtime dimension, not a structural one.
70
+ - Generated types are intentionally conservative and do not enforce locale completeness.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intor-cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "📟 CLI tool for intor",
5
5
  "author": "Yiming Liao",
6
6
  "homepage": "https://github.com/yiming-liao/intor-cli#readme",
@@ -39,11 +39,11 @@
39
39
  "intor": "src/cli/index.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@intor/reader-md": "0.1.1",
43
- "@intor/reader-yaml": "0.1.0",
42
+ "@intor/reader-md": "0.1.4",
43
+ "@intor/reader-yaml": "0.1.1",
44
44
  "cac": "6.7.14",
45
45
  "fast-glob": "3.3.3",
46
- "intor": "2.3.22",
46
+ "intor": "2.3.26",
47
47
  "logry": "2.1.6",
48
48
  "ora": "9.0.0",
49
49
  "picocolors": "1.1.1",
@@ -1,6 +1,6 @@
1
- import type { BuildInput, GeneratedSchema } from "../types";
1
+ import type { BuildInput, Schema } from "../types";
2
2
 
3
- export function buildSchemas(inputs: BuildInput[]): GeneratedSchema {
3
+ export function buildSchemas(inputs: BuildInput[]): Schema {
4
4
  return {
5
5
  version: 1,
6
6
  generatedAt: new Date().toISOString(),
@@ -1,4 +1,4 @@
1
- import type { InferNode } from "../../../core/infer-schema";
1
+ import type { InferNode } from "../../../core";
2
2
  import { indent } from "./indent";
3
3
 
4
4
  export function renderInferNode(node: InferNode, indentLevel = 4): string {
@@ -1,3 +1,3 @@
1
1
  export { buildTypes } from "./build-types";
2
2
  export { buildSchemas } from "./build-schemas";
3
- export type { BuildInput, GeneratedSchema } from "./types";
3
+ export type { BuildInput, Schema } from "./types";
@@ -7,12 +7,13 @@ export interface BuildInput {
7
7
  }
8
8
 
9
9
  // Schema
10
- export interface GeneratedSchema {
10
+ export interface Schema {
11
11
  version: number;
12
12
  generatedAt: string;
13
- configs: GeneratedSchemaConfig[];
13
+ configs: SchemaConfig[];
14
14
  }
15
- export interface GeneratedSchemaConfig {
15
+
16
+ export interface SchemaConfig {
16
17
  id: string;
17
18
  locales: readonly string[];
18
19
  schemas: InferredSchemas;
@@ -1,4 +1,3 @@
1
- import type { ReadGeneratedSchemaOptions } from "../../core";
2
1
  import type { ExtractUsagesOptions } from "../../core/extract-usages/extract-usages";
3
2
  import type { CAC } from "cac";
4
3
  import { check } from "../../features";
@@ -13,20 +12,6 @@ export function registerCheckCommand(cli: CAC) {
13
12
  // -----------------------------------------------------------------------
14
13
  // Option
15
14
  // -----------------------------------------------------------------------
16
- // Read generated schema options
17
- .option(
18
- "--cwd <path>",
19
- "Working directory for resolving intor config (default: process.cwd())",
20
- )
21
- .option(
22
- "--file-name <name>",
23
- "Generated schema file name (default: intor-generated.schema.json)",
24
- )
25
- .option(
26
- "--out-dir <path>",
27
- "Directory of generated schema output (default: .intor)",
28
- )
29
- // Extract usages options
30
15
  .option(
31
16
  "--tsconfig <path>",
32
17
  "Path to tsconfig.json (default: tsconfig.json)",
@@ -36,15 +21,13 @@ export function registerCheckCommand(cli: CAC) {
36
21
  // -----------------------------------------------------------------------
37
22
  // Action
38
23
  // -----------------------------------------------------------------------
39
- .action(
40
- async (options: ReadGeneratedSchemaOptions & ExtractUsagesOptions) => {
41
- const { cwd, fileName, outDir, tsconfigPath, debug } = options;
42
- try {
43
- await check({ cwd, fileName, outDir }, { tsconfigPath, debug });
44
- } catch (error) {
45
- console.error(error);
46
- process.exitCode = 1;
47
- }
48
- },
49
- );
24
+ .action(async (options: ExtractUsagesOptions) => {
25
+ const { tsconfigPath, debug } = options;
26
+ try {
27
+ await check({ tsconfigPath, debug });
28
+ } catch (error) {
29
+ console.error(error);
30
+ process.exitCode = 1;
31
+ }
32
+ });
50
33
  }
@@ -1,6 +1,7 @@
1
1
  import type { ExtraExt } from "../../core";
2
2
  import type { CAC } from "cac";
3
3
  import { generate } from "../../features";
4
+ import { normalizeReaderOptions } from "./utils/normalize-reader-options";
4
5
 
5
6
  export function registerGenerateCommand(cli: CAC) {
6
7
  cli
@@ -37,19 +38,7 @@ export function registerGenerateCommand(cli: CAC) {
37
38
  debug?: boolean;
38
39
  };
39
40
 
40
- // Normalize exts
41
- const exts = ext ? (Array.isArray(ext) ? ext : [ext]) : [];
42
-
43
- // Parse customReaders: ["md=./my-md-reader.ts"] → { md: "./my-md-reader.ts" }
44
- let customReaders: Record<string, string> | undefined;
45
- if (reader && reader.length > 0) {
46
- customReaders = {};
47
- for (const item of reader) {
48
- const [key, value] = item.split("=", 2);
49
- if (!key || !value) continue;
50
- customReaders[key] = value;
51
- }
52
- }
41
+ const { exts, customReaders } = normalizeReaderOptions({ ext, reader });
53
42
 
54
43
  try {
55
44
  await generate({ exts, customReaders, debug });
@@ -0,0 +1,46 @@
1
+ import type { ExtraExt } from "../../../core";
2
+
3
+ export interface NormalizedReaderOptions {
4
+ exts: ExtraExt[];
5
+ customReaders?: Record<string, string>;
6
+ }
7
+
8
+ export interface RawReaderOptions {
9
+ ext?: string | string[];
10
+ reader?: string[];
11
+ }
12
+
13
+ /**
14
+ * Normalize CLI reader-related options:
15
+ * - ext: string | string[] → string[]
16
+ * - reader: ["md=./reader.ts"] → { md: "./reader.ts" }
17
+ */
18
+ export function normalizeReaderOptions(
19
+ options: RawReaderOptions,
20
+ ): NormalizedReaderOptions {
21
+ // Normalize exts
22
+ const exts = options.ext
23
+ ? Array.isArray(options.ext)
24
+ ? options.ext
25
+ : [options.ext]
26
+ : [];
27
+
28
+ // Normalize custom readers
29
+ let customReaders: Record<string, string> | undefined;
30
+
31
+ if (options.reader && options.reader.length > 0) {
32
+ customReaders = {};
33
+
34
+ for (const item of options.reader) {
35
+ const [key, value] = item.split("=", 2);
36
+ if (!key || !value) continue;
37
+
38
+ customReaders[key] = value;
39
+ }
40
+ }
41
+
42
+ return {
43
+ exts: exts as ExtraExt[],
44
+ customReaders,
45
+ };
46
+ }
@@ -0,0 +1,50 @@
1
+ import type { ExtraExt } from "../../core";
2
+ import type { CAC } from "cac";
3
+ import { validate } from "../../features";
4
+ import { normalizeReaderOptions } from "./utils/normalize-reader-options";
5
+
6
+ export function registerValidateCommand(cli: CAC) {
7
+ cli
8
+ // -----------------------------------------------------------------------
9
+ // Command
10
+ // -----------------------------------------------------------------------
11
+ .command("validate", "Validate intor locale translations")
12
+
13
+ // -----------------------------------------------------------------------
14
+ // Option
15
+ // -----------------------------------------------------------------------
16
+ .option(
17
+ "--ext <ext>",
18
+ "Enable extra messages file extension (repeatable)",
19
+ { default: [] },
20
+ )
21
+ .option(
22
+ "--reader <mapping>",
23
+ "Custom reader mapping in the form <ext=path> (repeatable)",
24
+ { default: [] },
25
+ )
26
+ .option(
27
+ "--debug",
28
+ "Print debug information during config discovery and generation",
29
+ )
30
+
31
+ // -----------------------------------------------------------------------
32
+ // Action
33
+ // -----------------------------------------------------------------------
34
+ .action(async (options) => {
35
+ const { ext, reader, debug } = options as {
36
+ ext?: Array<ExtraExt>;
37
+ reader?: string[];
38
+ debug?: boolean;
39
+ };
40
+
41
+ const { exts, customReaders } = normalizeReaderOptions({ ext, reader });
42
+
43
+ try {
44
+ await validate({ exts, customReaders, debug });
45
+ } catch (error) {
46
+ console.error(error);
47
+ process.exitCode = 1;
48
+ }
49
+ });
50
+ }
package/src/cli/index.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import { cac } from "cac";
4
4
  import { registerCheckCommand } from "./commands/check";
5
5
  import { registerGenerateCommand } from "./commands/generate";
6
+ import { registerValidateCommand } from "./commands/validate";
6
7
 
7
8
  const cli = cac("intor");
8
9
 
@@ -11,6 +12,7 @@ const cli = cac("intor");
11
12
  // ---------------------------------------------------------------------
12
13
  registerGenerateCommand(cli);
13
14
  registerCheckCommand(cli);
15
+ registerValidateCommand(cli);
14
16
 
15
17
  // ---------------------------------------------------------------------
16
18
  // Global options / help
@@ -0,0 +1,27 @@
1
+ import type { ExtraExt } from "../constants";
2
+ import type { IntorResolvedConfig, MessageObject } from "intor";
3
+ import { collectRuntimeMessages } from "./collect-runtime-messages";
4
+
5
+ export async function collectOtherLocaleMessages(
6
+ config: IntorResolvedConfig,
7
+ exts: Array<ExtraExt> = [],
8
+ customReaders?: Record<string, string>,
9
+ ): Promise<Record<string, MessageObject>> {
10
+ const { supportedLocales, defaultLocale } = config;
11
+
12
+ const result: Record<string, MessageObject> = {};
13
+
14
+ for (const locale of supportedLocales) {
15
+ if (locale === defaultLocale) continue;
16
+
17
+ const { messages } = await collectRuntimeMessages(
18
+ config,
19
+ locale,
20
+ exts,
21
+ customReaders,
22
+ );
23
+ result[locale] = messages[locale];
24
+ }
25
+
26
+ return result;
27
+ }
@@ -1 +1,2 @@
1
1
  export { collectRuntimeMessages } from "./collect-runtime-messages";
2
+ export { collectOtherLocaleMessages } from "./collect-other-locale-messages";
@@ -0,0 +1,5 @@
1
+ export const DEFAULT_OUT_DIR = ".intor";
2
+ export const DEFAULT_TYPES_FILE_NAME = "types.d.ts";
3
+ export const DEFAULT_SCHEMA_FILE_NAME = "schema.json";
4
+
5
+ export const DEFAULT_MESSAGES_SNAPSHOT_DIR = "messages-snapshot";
@@ -1,7 +1,8 @@
1
1
  export {
2
2
  DEFAULT_OUT_DIR,
3
- DEFAULT_TYPES_FILE,
4
- DEFAULT_SCHEMA_FILE,
5
- } from "./generated-files";
3
+ DEFAULT_TYPES_FILE_NAME,
4
+ DEFAULT_SCHEMA_FILE_NAME,
5
+ DEFAULT_MESSAGES_SNAPSHOT_DIR,
6
+ } from "./generated";
6
7
 
7
8
  export { EXTRA_EXTS, type ExtraExt } from "./extra-exts";
@@ -1,4 +1,4 @@
1
- export { extractUsages } from "./extract-usages";
1
+ export { extractUsages, type ExtractUsagesOptions } from "./extract-usages";
2
2
 
3
3
  export type {
4
4
  ExtractedUsages,
@@ -0,0 +1,11 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ export async function ensureAndWrite(
5
+ filePath: string,
6
+ content: string,
7
+ ): Promise<string> {
8
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
9
+ await fs.writeFile(filePath, content, "utf8");
10
+ return filePath;
11
+ }
@@ -0,0 +1,6 @@
1
+ export { writeTypes } from "./write-types";
2
+
3
+ export { writeSchema } from "./write-schema";
4
+ export { readSchema } from "./read-schema";
5
+
6
+ export { writeMessagesSnapshot } from "./write-messages-snapshot";
@@ -0,0 +1,33 @@
1
+ import type { Schema } from "../../build";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../constants";
5
+
6
+ export async function readSchema(): Promise<Schema> {
7
+ const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
8
+
9
+ let raw: string;
10
+
11
+ // ------------------------------------------------------------------
12
+ // Read file
13
+ // ------------------------------------------------------------------
14
+ try {
15
+ raw = await fs.readFile(filePath, "utf8");
16
+ } catch {
17
+ throw new Error(
18
+ `Failed to read Intor schema file at "${filePath}".\n` +
19
+ `Have you run "intor generate"?`,
20
+ );
21
+ }
22
+
23
+ // ------------------------------------------------------------------
24
+ // Parse JSON
25
+ // ------------------------------------------------------------------
26
+ try {
27
+ return JSON.parse(raw) as Schema;
28
+ } catch {
29
+ throw new Error(
30
+ `Invalid JSON format in Intor schema file at "${filePath}".`,
31
+ );
32
+ }
33
+ }
@@ -0,0 +1,27 @@
1
+ /* eslint-disable unicorn/no-array-sort */
2
+ import type { MessageObject } from "intor";
3
+ import path from "node:path";
4
+ import { DEFAULT_OUT_DIR, DEFAULT_MESSAGES_SNAPSHOT_DIR } from "../constants";
5
+ import { ensureAndWrite } from "./ensure-and-write";
6
+
7
+ export async function writeMessagesSnapshot(
8
+ localeMessages: Record<string, MessageObject>,
9
+ ): Promise<string[]> {
10
+ const filePaths: string[] = [];
11
+
12
+ for (const locale of Object.keys(localeMessages).sort()) {
13
+ const messages = localeMessages[locale];
14
+
15
+ const filePath = path.join(
16
+ DEFAULT_OUT_DIR,
17
+ DEFAULT_MESSAGES_SNAPSHOT_DIR,
18
+ `${locale}.json`,
19
+ );
20
+
21
+ await ensureAndWrite(filePath, JSON.stringify(messages, null, 2));
22
+
23
+ filePaths.push(filePath);
24
+ }
25
+
26
+ return filePaths;
27
+ }
@@ -0,0 +1,9 @@
1
+ import type { Schema } from "../../build";
2
+ import path from "node:path";
3
+ import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../constants";
4
+ import { ensureAndWrite } from "./ensure-and-write";
5
+
6
+ export async function writeSchema(schema: Schema): Promise<string> {
7
+ const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
8
+ return await ensureAndWrite(filePath, JSON.stringify(schema, null, 2));
9
+ }
@@ -0,0 +1,8 @@
1
+ import path from "node:path";
2
+ import { DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME } from "../constants";
3
+ import { ensureAndWrite } from "./ensure-and-write";
4
+
5
+ export async function writeTypes(types: string): Promise<string> {
6
+ const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME);
7
+ return await ensureAndWrite(filePath, types);
8
+ }
package/src/core/index.ts CHANGED
@@ -1,13 +1,40 @@
1
+ // discover-configs
1
2
  export { discoverConfigs } from "./discover-configs";
2
- export { collectRuntimeMessages } from "./collect-messages";
3
- export { inferSchemas, type InferredSchemas } from "./infer-schema";
4
- export { extractUsages } from "./extract-usages";
5
- export { collectDiagnostics, groupDiagnostics } from "./diagnostics";
6
3
 
7
- export { writeGeneratedFiles } from "./write-generated-files";
4
+ // collect-messages
8
5
  export {
9
- readGeneratedSchema,
10
- type ReadGeneratedSchemaOptions,
11
- } from "./read-generated-schema";
6
+ collectRuntimeMessages,
7
+ collectOtherLocaleMessages,
8
+ } from "./collect-messages";
12
9
 
13
- export { EXTRA_EXTS, type ExtraExt } from "./constants";
10
+ // infer-schema
11
+ export {
12
+ inferSchemas,
13
+ type InferNode,
14
+ type InferredSchemas,
15
+ extractInterpolationNames,
16
+ } from "./infer-schema";
17
+
18
+ // extract-usages
19
+ export {
20
+ extractUsages,
21
+ type ExtractUsagesOptions,
22
+ type ExtractedUsages,
23
+ type TranslatorFactory,
24
+ type TranslatorMethod,
25
+ type ReplacementUsage,
26
+ type PreKeyUsage,
27
+ type KeyUsage,
28
+ type RichUsage,
29
+ } from "./extract-usages";
30
+
31
+ // generated
32
+ export {
33
+ writeTypes,
34
+ writeSchema,
35
+ readSchema,
36
+ writeMessagesSnapshot,
37
+ } from "./generated";
38
+
39
+ // constants
40
+ export { DEFAULT_OUT_DIR, EXTRA_EXTS, type ExtraExt } from "./constants";
@@ -1,3 +1,4 @@
1
1
  export { inferSchemas } from "./infer-schemas";
2
2
  export type { InferNode, InferredSchemas } from "./types";
3
- export { buildSchemas } from "../../build/build-schemas/build-schemas";
3
+
4
+ export { extractInterpolationNames } from "./replacements/extract-interpolation-names";
@@ -3,14 +3,18 @@ import type { MessageObject } from "intor";
3
3
  import { inferMessagesSchema } from "./messages";
4
4
  import { inferReplacementsSchema } from "./replacements";
5
5
  import { inferRichSchema } from "./rich";
6
+ import { stripInternalKeys } from "./utils/strip-internal-keys";
6
7
 
7
8
  /**
8
9
  * Infer all semantic schemas from messages.
9
10
  */
10
11
  export function inferSchemas(messages: MessageObject): InferredSchemas {
11
- return {
12
+ const schemas = {
12
13
  messagesSchema: inferMessagesSchema(messages),
13
14
  replacementsSchema: inferReplacementsSchema(messages),
14
15
  richSchema: inferRichSchema(messages),
15
16
  };
17
+
18
+ stripInternalKeys(schemas);
19
+ return schemas;
16
20
  }
@@ -49,7 +49,7 @@ function inferValue(value: MessageValue): InferNode {
49
49
  // Object values
50
50
  // ----------------------------------------------------------------------
51
51
  if (isMessageObject(value)) {
52
- const result = inferObject(value, inferValue);
52
+ const result = inferObject(value, inferValue, "messages");
53
53
 
54
54
  // empty object → fallback record
55
55
  if (result.kind === "none") {
@@ -55,7 +55,7 @@ function inferValue(value: MessageValue): InferNode {
55
55
  // Object values (delegate aggregation & pruning)
56
56
  // ----------------------------------------------------------------------
57
57
  if (isMessageObject(value)) {
58
- return inferObject(value, inferValue);
58
+ return inferObject(value, inferValue, "replacements");
59
59
  }
60
60
 
61
61
  // Fallback
@@ -58,7 +58,7 @@ function inferValue(value: MessageValue): InferNode {
58
58
  // Object values (delegate aggregation & pruning)
59
59
  // ----------------------------------------------------------------------
60
60
  if (isMessageObject(value)) {
61
- return inferObject(value, inferValue);
61
+ return inferObject(value, inferValue, "rich");
62
62
  }
63
63
 
64
64
  // Fallback
@@ -1,5 +1,6 @@
1
1
  import type { InferNode } from "../types";
2
2
  import type { MessageObject, MessageValue } from "intor";
3
+ import { shouldSkipKey } from "./should-skip-key";
3
4
 
4
5
  /**
5
6
  * Infer an object-like semantic node by aggregating inferred children.
@@ -8,13 +9,17 @@ import type { MessageObject, MessageValue } from "intor";
8
9
  * - Prunes branches without semantic meaning
9
10
  * - Returns `none` if no children remain
10
11
  */
12
+
11
13
  export function inferObject(
12
14
  value: MessageObject,
13
15
  inferChild: (value: MessageValue) => InferNode,
16
+ mode: "messages" | "replacements" | "rich",
14
17
  ): InferNode {
15
18
  const properties: Record<string, InferNode> = {};
16
19
 
17
20
  for (const [key, val] of Object.entries(value)) {
21
+ if (shouldSkipKey(key, mode)) continue;
22
+
18
23
  const child = inferChild(val);
19
24
 
20
25
  // Skip branches without semantic meaning
@@ -23,10 +28,7 @@ export function inferObject(
23
28
  properties[key] = child;
24
29
  }
25
30
 
26
- // No inferred children => no semantic result
27
- if (Object.keys(properties).length === 0) {
28
- return { kind: "none" };
29
- }
30
-
31
- return { kind: "object", properties };
31
+ return Object.keys(properties).length === 0
32
+ ? { kind: "none" }
33
+ : { kind: "object", properties };
32
34
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Determine whether a message object key should be ignored during schema inference.
3
+ *
4
+ * - Internal Intor metadata keys (`__intor_*`) are never part of semantic schemas
5
+ * - Markdown payload (`content`) is only meaningful in Messages schema,
6
+ * and must be excluded from Replacements / Rich inference
7
+ */
8
+ export function shouldSkipKey(
9
+ key: string,
10
+ mode: "messages" | "replacements" | "rich",
11
+ ) {
12
+ if (key.startsWith("__intor_")) return true;
13
+ if (mode !== "messages" && key === "content") return true;
14
+ return false;
15
+ }