intor-cli 0.0.14 → 0.0.16

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 (117) hide show
  1. package/package.json +2 -3
  2. package/src/cli/commands/check.ts +23 -17
  3. package/src/cli/commands/discover.ts +32 -0
  4. package/src/cli/commands/generate.ts +35 -40
  5. package/src/cli/commands/index.ts +4 -0
  6. package/src/cli/commands/options/index.ts +1 -0
  7. package/src/cli/commands/options/options.ts +55 -0
  8. package/src/cli/commands/utils/normalize-message-files.ts +49 -0
  9. package/src/cli/commands/utils/normalize-reader-options.ts +15 -28
  10. package/src/cli/commands/validate.ts +26 -30
  11. package/src/cli/index.ts +38 -23
  12. package/src/cli/menu/index.ts +1 -0
  13. package/src/cli/menu/prompts/prompt-check.ts +74 -0
  14. package/src/cli/menu/prompts/prompt-discover.ts +25 -0
  15. package/src/cli/menu/prompts/prompt-generate.ts +106 -0
  16. package/src/cli/menu/prompts/prompt-validate.ts +49 -0
  17. package/src/cli/menu/prompts/shared/prompt-reader-options.ts +63 -0
  18. package/src/cli/menu/prompts/shared/shared.ts +76 -0
  19. package/src/cli/menu/run.ts +72 -0
  20. package/src/cli/version.ts +3 -0
  21. package/src/constants.ts +6 -0
  22. package/src/core/artifacts/index.ts +5 -0
  23. package/src/core/artifacts/schema/build-schema.ts +13 -0
  24. package/src/core/artifacts/schema/index.ts +3 -0
  25. package/src/core/{generated → artifacts/schema}/read-schema.ts +4 -4
  26. package/src/core/artifacts/schema/write-schema.ts +14 -0
  27. package/src/{build/build-types → core/artifacts/types/build}/build-types.ts +9 -10
  28. package/src/{build/build-types → core/artifacts/types/build}/utils/normalize-rich-infer-node.ts +1 -1
  29. package/src/{build/build-types → core/artifacts/types/build}/utils/render-infer-node.ts +1 -1
  30. package/src/core/artifacts/types/index.ts +2 -0
  31. package/src/core/artifacts/types/write-types.ts +8 -0
  32. package/src/core/artifacts/types.ts +20 -0
  33. package/src/core/collect-messages/collect-other-locale-messages.ts +5 -7
  34. package/src/core/collect-messages/collect-runtime-messages.ts +8 -6
  35. package/src/core/collect-messages/index.ts +1 -0
  36. package/src/core/collect-messages/readers.ts +1 -0
  37. package/src/core/collect-messages/types.ts +7 -1
  38. package/src/core/constants/index.ts +2 -0
  39. package/src/core/discover-configs/discover-configs.ts +47 -26
  40. package/src/core/extract-usages/extract-usages.ts +33 -24
  41. package/src/core/index.ts +12 -7
  42. package/src/core/infer-shape/index.ts +4 -0
  43. package/src/core/{infer-schema/messages/infer-messages-schema.ts → infer-shape/infer-messages-shape.ts} +5 -10
  44. package/src/core/{infer-schema/replacements/infer-replacements-schema.ts → infer-shape/infer-replacements-shape.ts} +6 -11
  45. package/src/core/{infer-schema/rich/infer-rich-schema.ts → infer-shape/infer-rich-shape.ts} +5 -10
  46. package/src/core/infer-shape/infer-shapes.ts +21 -0
  47. package/src/core/{infer-schema → infer-shape}/types.ts +4 -4
  48. package/src/core/scan/index.ts +2 -0
  49. package/src/core/{extract-usages/load-source-files-from-tscofnig.ts → scan/load-source-files.ts} +34 -15
  50. package/src/core/scan/scan-files.ts +25 -0
  51. package/src/features/check/build-scoped-usages.ts +35 -0
  52. package/src/features/check/check.ts +51 -53
  53. package/src/features/check/diagnostics/collect.ts +6 -2
  54. package/src/features/check/diagnostics/group.ts +0 -1
  55. package/src/features/check/index.ts +1 -0
  56. package/src/features/check/render-config-summary.ts +47 -0
  57. package/src/features/check/types.ts +12 -0
  58. package/src/features/discover/discover.ts +22 -0
  59. package/src/features/discover/index.ts +1 -0
  60. package/src/features/generate/generate.ts +56 -49
  61. package/src/features/generate/index.ts +1 -0
  62. package/src/features/generate/render-overrides.ts +73 -0
  63. package/src/features/generate/render-summary.ts +28 -0
  64. package/src/features/generate/types.ts +12 -0
  65. package/src/features/generate/utils/resolve-message-source.ts +20 -0
  66. package/src/features/generate/utils/validate-message-source.ts +53 -0
  67. package/src/features/index.ts +4 -3
  68. package/src/features/shared/to-relative-path.ts +10 -0
  69. package/src/features/shared/write-json-report.ts +19 -0
  70. package/src/features/validate/index.ts +1 -0
  71. package/src/features/validate/{messages/validate-messages-schema.ts → missing/collect-missing-messages.ts} +5 -5
  72. package/src/features/validate/{replacements/validate-replacements-schema.ts → missing/collect-missing-replacements.ts} +4 -4
  73. package/src/features/validate/missing/collect-missing-requirements.ts +44 -0
  74. package/src/features/validate/{rich/validate-rich-schema.ts → missing/collect-missing-rich.ts} +5 -5
  75. package/src/features/validate/render-config-summary.ts +47 -0
  76. package/src/features/validate/render-locale-blocks.ts +56 -0
  77. package/src/features/validate/types.ts +14 -0
  78. package/src/features/validate/validate.ts +38 -43
  79. package/src/logger.ts +95 -0
  80. package/src/render.ts +57 -0
  81. package/src/build/build-schemas/build-schemas.ts +0 -13
  82. package/src/build/build-schemas/index.ts +0 -1
  83. package/src/build/index.ts +0 -3
  84. package/src/build/types.ts +0 -20
  85. package/src/core/generated/index.ts +0 -6
  86. package/src/core/generated/write-messages-snapshot.ts +0 -27
  87. package/src/core/generated/write-schema.ts +0 -9
  88. package/src/core/generated/write-types.ts +0 -8
  89. package/src/core/infer-schema/index.ts +0 -4
  90. package/src/core/infer-schema/infer-schemas.ts +0 -20
  91. package/src/core/infer-schema/messages/index.ts +0 -1
  92. package/src/core/infer-schema/replacements/index.ts +0 -1
  93. package/src/core/infer-schema/rich/index.ts +0 -1
  94. package/src/core/scan-logger.ts +0 -10
  95. package/src/features/check/print-summary.ts +0 -28
  96. package/src/features/generate/print-configs.ts +0 -8
  97. package/src/features/generate/print-overrides.ts +0 -62
  98. package/src/features/generate/print-summary.ts +0 -26
  99. package/src/features/print.ts +0 -43
  100. package/src/features/validate/messages/index.ts +0 -1
  101. package/src/features/validate/print-summary.ts +0 -65
  102. package/src/features/validate/replacements/index.ts +0 -1
  103. package/src/features/validate/rich/index.ts +0 -1
  104. package/src/features/validate/validate-locale-messages.ts +0 -38
  105. /package/src/core/{generated → artifacts}/ensure-and-write.ts +0 -0
  106. /package/src/{build/build-types → core/artifacts/types/build}/index.ts +0 -0
  107. /package/src/{build/build-types → core/artifacts/types/build}/output/append-config-block.ts +0 -0
  108. /package/src/{build/build-types → core/artifacts/types/build}/output/append-footer.ts +0 -0
  109. /package/src/{build/build-types → core/artifacts/types/build}/output/append-header.ts +0 -0
  110. /package/src/{build/build-types → core/artifacts/types/build}/output/index.ts +0 -0
  111. /package/src/{build/build-types → core/artifacts/types/build}/utils/indent.ts +0 -0
  112. /package/src/core/{infer-schema/replacements → infer-shape/utils}/extract-interpolation-names.ts +0 -0
  113. /package/src/core/{infer-schema → infer-shape}/utils/infer-object.ts +0 -0
  114. /package/src/core/{infer-schema → infer-shape}/utils/is-message-object.ts +0 -0
  115. /package/src/core/{infer-schema → infer-shape}/utils/should-skip-key.ts +0 -0
  116. /package/src/core/{infer-schema → infer-shape}/utils/strip-internal-keys.ts +0 -0
  117. /package/src/features/{spinner.ts → shared/spinner.ts} +0 -0
@@ -0,0 +1,106 @@
1
+ import type { GenerateOptions } from "../../../features";
2
+ import { text, isCancel, confirm } from "@clack/prompts";
3
+ import { discoverConfigs } from "../../../core";
4
+ import { promptReaderOptions } from "./shared/prompt-reader-options";
5
+ import { promptMode, promptDebug, printOptionsSummary } from "./shared/shared";
6
+
7
+ export async function promptGenerate(): Promise<GenerateOptions | null> {
8
+ // ------------------------------------------------------------------
9
+ // Discover configs early (for prompt decisions)
10
+ // ------------------------------------------------------------------
11
+ const configs = await discoverConfigs();
12
+ if (configs.length === 0) throw new Error("No Intor config found.");
13
+ const isSingleConfig = configs.length === 1;
14
+
15
+ // ------------------------------------------------------------------
16
+ // Mode
17
+ // ------------------------------------------------------------------
18
+ const mode = await promptMode();
19
+ if (!mode) return null;
20
+ if (mode === "default") return {};
21
+
22
+ const options: GenerateOptions = {};
23
+
24
+ // ------------------------------------------------------------------
25
+ // Message source
26
+ // ------------------------------------------------------------------
27
+ const useCustomMessages = await confirm({
28
+ message:
29
+ "Do you want to provide message files instead of using the loader?",
30
+ initialValue: false,
31
+ });
32
+ if (isCancel(useCustomMessages)) return null;
33
+
34
+ if (useCustomMessages) {
35
+ const sourceMode = isSingleConfig ? "single" : "mapping";
36
+
37
+ // single mode
38
+ if (sourceMode === "single") {
39
+ const file = await text({
40
+ message: "Path to the message file (default locale)",
41
+ placeholder: "messages/en/index.json",
42
+ });
43
+ if (isCancel(file)) return null;
44
+ options.messageSource = { mode: "single", file };
45
+ }
46
+
47
+ // mapping mode
48
+ if (sourceMode === "mapping") {
49
+ const files: Record<string, string> = {};
50
+ for (const { config } of configs) {
51
+ const path = await text({
52
+ message: `Message file for config "${config.id}" (default locale)`,
53
+ placeholder: "messages/en/index.json",
54
+ });
55
+ if (isCancel(path)) return null;
56
+ if (path) files[config.id] = path;
57
+ }
58
+ options.messageSource = { mode: "mapping", files };
59
+ }
60
+ }
61
+
62
+ // ------------------------------------------------------------------
63
+ // Reader options
64
+ // ------------------------------------------------------------------
65
+ const readerOptions = await promptReaderOptions();
66
+ if (readerOptions === null) return null;
67
+ if (readerOptions.exts?.length) {
68
+ options.exts = readerOptions.exts;
69
+ }
70
+ if (readerOptions.customReaders) {
71
+ options.customReaders = readerOptions.customReaders;
72
+ }
73
+
74
+ // ------------------------------------------------------------------
75
+ // Debug
76
+ // ------------------------------------------------------------------
77
+ const debug = await promptDebug();
78
+ if (debug === null) return null;
79
+ if (debug) options.debug = true;
80
+
81
+ // ------------------------------------------------------------------
82
+ // Summary
83
+ // ------------------------------------------------------------------
84
+ printOptionsSummary("Generate options", [
85
+ [
86
+ "message files",
87
+ options.messageSource?.mode === "single"
88
+ ? options.messageSource.file
89
+ : options.messageSource?.mode === "mapping"
90
+ ? Object.entries(options.messageSource.files)
91
+ .map(([id, path]) => `${id}: ${path}`)
92
+ .join(", ")
93
+ : "(loader)",
94
+ ],
95
+ ["exts", options.exts?.join(", ") ?? "(none)"],
96
+ [
97
+ "custom readers",
98
+ options.customReaders
99
+ ? Object.keys(options.customReaders).join(", ")
100
+ : "(none)",
101
+ ],
102
+ ["debug", options.debug ? "on" : "off"],
103
+ ]);
104
+
105
+ return options;
106
+ }
@@ -0,0 +1,49 @@
1
+ import type { ValidateOptions } from "../../../features";
2
+ import { promptReaderOptions } from "./shared/prompt-reader-options";
3
+ import { promptMode, promptDebug, printOptionsSummary } from "./shared/shared";
4
+
5
+ export async function promptValidate(): Promise<ValidateOptions | null> {
6
+ // ------------------------------------------------------------------
7
+ // Mode
8
+ // ------------------------------------------------------------------
9
+ const mode = await promptMode();
10
+ if (!mode) return null;
11
+ if (mode === "default") return {};
12
+
13
+ const options: ValidateOptions = {};
14
+
15
+ // ------------------------------------------------------------------
16
+ // Reader options
17
+ // ------------------------------------------------------------------
18
+ const readerOptions = await promptReaderOptions();
19
+ if (readerOptions === null) return null;
20
+ if (readerOptions.exts?.length) {
21
+ options.exts = readerOptions.exts;
22
+ }
23
+ if (readerOptions.customReaders) {
24
+ options.customReaders = readerOptions.customReaders;
25
+ }
26
+
27
+ // ------------------------------------------------------------------
28
+ // Debug
29
+ // ------------------------------------------------------------------
30
+ const debug = await promptDebug();
31
+ if (debug === null) return null;
32
+ if (debug) options.debug = true;
33
+
34
+ // ------------------------------------------------------------------
35
+ // Summary
36
+ // ------------------------------------------------------------------
37
+ printOptionsSummary("Validate options", [
38
+ ["exts", options.exts?.join(", ") ?? "(none)"],
39
+ [
40
+ "custom readers",
41
+ options.customReaders
42
+ ? Object.keys(options.customReaders).join(", ")
43
+ : "(none)",
44
+ ],
45
+ ["debug", options.debug ? "on" : "off"],
46
+ ]);
47
+
48
+ return options;
49
+ }
@@ -0,0 +1,63 @@
1
+ import type { ReaderOptions, ExtraExt } from "../../../../core";
2
+ import { confirm, multiselect, text, isCancel } from "@clack/prompts";
3
+
4
+ type FormatOption = ExtraExt | "custom";
5
+
6
+ export async function promptReaderOptions(): Promise<ReaderOptions | null> {
7
+ const enable = await confirm({
8
+ message: "Enable additional message readers?",
9
+ initialValue: false,
10
+ });
11
+ if (isCancel(enable)) return null;
12
+ if (!enable) return { exts: [], customReaders: undefined };
13
+
14
+ // --------------------------------------------------
15
+ // Select built-in formats and/or custom reader
16
+ // --------------------------------------------------
17
+ const selected = await multiselect<FormatOption>({
18
+ message: "Select message readers",
19
+ options: [
20
+ { value: "md", label: "Markdown (.md)" },
21
+ { value: "yaml", label: "YAML (.yaml/.yml)" },
22
+ { value: "toml", label: "TOML (.toml)" },
23
+ { value: "json5", label: "JSON5 (.json5)" },
24
+ { value: "custom", label: "Custom reader" },
25
+ ],
26
+ required: false,
27
+ });
28
+ if (isCancel(selected)) return null;
29
+
30
+ // Extract built-in extensions
31
+ const exts = selected.filter((v): v is ExtraExt => v !== "custom");
32
+
33
+ let customReaders: Record<string, string> | undefined;
34
+
35
+ // --------------------------------------------------
36
+ // Custom reader mappings (ext=path)
37
+ // --------------------------------------------------
38
+ if (selected.includes("custom")) {
39
+ const mapping = await text({
40
+ message: "Custom reader mappings (ext=path, comma separated)",
41
+ placeholder: "md=./reader-md.ts",
42
+ });
43
+ if (isCancel(mapping)) return null;
44
+
45
+ customReaders = Object.fromEntries(
46
+ mapping
47
+ .split(",")
48
+ .map((pair) => pair.trim())
49
+ .filter(Boolean)
50
+ .map((pair) => {
51
+ const [ext, path] = pair.split("=", 2);
52
+ if (!ext || !path) {
53
+ throw new Error(
54
+ `Invalid custom reader entry: "${pair}". Expected <ext=path>.`,
55
+ );
56
+ }
57
+ return [ext, path];
58
+ }),
59
+ );
60
+ }
61
+
62
+ return { exts, customReaders };
63
+ }
@@ -0,0 +1,76 @@
1
+ import { select, isCancel, confirm, text, note } from "@clack/prompts";
2
+
3
+ // ------------------------------------------------------------------
4
+ // Mode
5
+ // ------------------------------------------------------------------
6
+ export async function promptMode(): Promise<"default" | "custom" | null> {
7
+ const mode = await select({
8
+ message: "Mode?",
9
+ options: [
10
+ { value: "default", label: "Default (recommended)" },
11
+ { value: "custom", label: "Custom configuration" },
12
+ ],
13
+ });
14
+ if (isCancel(mode)) return null;
15
+ return mode;
16
+ }
17
+
18
+ // ------------------------------------------------------------------
19
+ // Format
20
+ // ------------------------------------------------------------------
21
+ export async function promptFormat(): Promise<"human" | "json" | null> {
22
+ const format = await select({
23
+ message: "Select output format",
24
+ options: [
25
+ { value: "human", label: "Human-readable" },
26
+ { value: "json", label: "JSON (machine-readable)" },
27
+ ],
28
+ initialValue: "human",
29
+ });
30
+ if (isCancel(format)) return null;
31
+ return format as "human" | "json";
32
+ }
33
+
34
+ // ------------------------------------------------------------------
35
+ // Output
36
+ // ------------------------------------------------------------------
37
+ export async function promptOutput(): Promise<string | undefined | null> {
38
+ const writeToFile = await confirm({
39
+ message: "Write output to a file?",
40
+ initialValue: false,
41
+ });
42
+ if (isCancel(writeToFile)) return null;
43
+
44
+ if (writeToFile) {
45
+ const output = await text({
46
+ message: "Output file path",
47
+ placeholder: "check-report.json",
48
+ defaultValue: "check-report.json",
49
+ });
50
+ if (isCancel(output)) return null;
51
+ return output || undefined;
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ // ------------------------------------------------------------------
57
+ // Debug
58
+ // ------------------------------------------------------------------
59
+ export async function promptDebug(): Promise<boolean | null> {
60
+ const debug = await confirm({
61
+ message: "Enable debug mode?",
62
+ initialValue: false,
63
+ });
64
+ if (isCancel(debug)) return null;
65
+ return debug;
66
+ }
67
+
68
+ // ------------------------------------------------------------------
69
+ // Summary
70
+ // ------------------------------------------------------------------
71
+ export function printOptionsSummary(
72
+ title: string,
73
+ lines: Array<[string, string]>,
74
+ ) {
75
+ note(lines.map(([k, v]) => `${k}: ${v}`).join("\n"), title);
76
+ }
@@ -0,0 +1,72 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import { outro, select, isCancel, intro } from "@clack/prompts";
3
+ import pc from "picocolors";
4
+ import { features } from "../../constants";
5
+ import { check, discover, generate, validate } from "../../features";
6
+ import { bold, italic } from "../../render";
7
+ import { version } from "../version";
8
+ import { promptCheck } from "./prompts/prompt-check";
9
+ import { promptDiscover } from "./prompts/prompt-discover";
10
+ import { promptGenerate } from "./prompts/prompt-generate";
11
+ import { promptValidate } from "./prompts/prompt-validate";
12
+
13
+ /**
14
+ * Run a single CLI action with its corresponding prompt and handler.
15
+ */
16
+ async function runAction<T>(
17
+ prompt: () => Promise<T | null>,
18
+ action: (options: T) => Promise<void>,
19
+ ) {
20
+ const options = await prompt();
21
+ if (!options) {
22
+ outro(pc.dim("Cancelled"));
23
+ process.exit(0);
24
+ }
25
+ await action(options);
26
+ }
27
+
28
+ /**
29
+ * Entry point for the interactive CLI menu.
30
+ */
31
+ export async function run() {
32
+ intro(italic(bold("The Intor CLI.")));
33
+
34
+ const action = await select({
35
+ message: "Select an action",
36
+ options: [
37
+ ...Object.values(features).map(({ name, title }) => ({
38
+ value: name,
39
+ label: title,
40
+ })),
41
+ { value: "exit", label: "Exit" },
42
+ ],
43
+ });
44
+
45
+ if (isCancel(action) || action === "exit") {
46
+ outro("Exited");
47
+ process.exit(0);
48
+ }
49
+
50
+ switch (action) {
51
+ case "discover": {
52
+ await runAction(promptDiscover, discover);
53
+ break;
54
+ }
55
+ case "check": {
56
+ await runAction(promptCheck, check);
57
+ break;
58
+ }
59
+ case "generate": {
60
+ await runAction(promptGenerate, (options) =>
61
+ generate({ ...options, toolVersion: version }),
62
+ );
63
+ break;
64
+ }
65
+ case "validate": {
66
+ await runAction(promptValidate, validate);
67
+ break;
68
+ }
69
+ }
70
+
71
+ outro(pc.green("Completed"));
72
+ }
@@ -0,0 +1,3 @@
1
+ import package_ from "../../package.json" with { type: "json" };
2
+
3
+ export const version = package_.version;
@@ -0,0 +1,6 @@
1
+ export const features = {
2
+ discover: { name: "discover", title: "Discover intor configs" },
3
+ generate: { name: "generate", title: "Generate types & schemas" },
4
+ check: { name: "check", title: "Check translation usages" },
5
+ validate: { name: "validate", title: "Validate messages" },
6
+ } as const;
@@ -0,0 +1,5 @@
1
+ export { buildTypes, writeTypes } from "./types/index";
2
+
3
+ export { buildSchema, writeSchema, readSchema } from "./schema";
4
+
5
+ export type { GeneratedSchema, SchemaEntry } from "./types";
@@ -0,0 +1,13 @@
1
+ import type { GeneratedSchema, SchemaEntry } from "../types";
2
+
3
+ export function buildSchema(
4
+ entries: SchemaEntry[],
5
+ toolVersion?: string,
6
+ ): GeneratedSchema {
7
+ return {
8
+ version: 1,
9
+ toolVersion,
10
+ generatedAt: new Date().toISOString(),
11
+ entries,
12
+ };
13
+ }
@@ -0,0 +1,3 @@
1
+ export { buildSchema } from "./build-schema";
2
+ export { readSchema } from "./read-schema";
3
+ export { writeSchema } from "./write-schema";
@@ -1,9 +1,9 @@
1
- import type { Schema } from "../../build";
1
+ import type { GeneratedSchema } from "../types";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../constants";
4
+ import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../../constants";
5
5
 
6
- export async function readSchema(): Promise<Schema> {
6
+ export async function readSchema(): Promise<GeneratedSchema> {
7
7
  const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
8
8
 
9
9
  let raw: string;
@@ -24,7 +24,7 @@ export async function readSchema(): Promise<Schema> {
24
24
  // Parse JSON
25
25
  // ------------------------------------------------------------------
26
26
  try {
27
- return JSON.parse(raw) as Schema;
27
+ return JSON.parse(raw) as GeneratedSchema;
28
28
  } catch {
29
29
  throw new Error(
30
30
  `Invalid JSON format in Intor schema file at "${filePath}".`,
@@ -0,0 +1,14 @@
1
+ import type { GeneratedSchema } from "../types";
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(
7
+ generatedSchema: GeneratedSchema,
8
+ ): Promise<string> {
9
+ const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
10
+ return await ensureAndWrite(
11
+ filePath,
12
+ JSON.stringify(generatedSchema, null, 2),
13
+ );
14
+ }
@@ -1,4 +1,4 @@
1
- import type { BuildInput } from "../types";
1
+ import type { SchemaEntry } from "../../types";
2
2
  import { appendHeader, appendConfigBlock, appendFooter } from "./output";
3
3
  import { normalizeRichInferNode } from "./utils/normalize-rich-infer-node";
4
4
  import { renderInferNode } from "./utils/render-infer-node";
@@ -8,20 +8,19 @@ const GENERATED_INTERFACE_NAME = "IntorGeneratedTypes";
8
8
  /**
9
9
  * Builds the global TypeScript declaration from inferred typegen inputs.
10
10
  */
11
- export function buildTypes(inputs: BuildInput[]): string {
11
+ export function buildTypes(schemaEntries: SchemaEntry[]): string {
12
12
  const lines: string[] = [];
13
13
 
14
14
  // Global declaration header
15
15
  appendHeader(lines, GENERATED_INTERFACE_NAME);
16
16
 
17
17
  // Per-config processing
18
- for (const [index, input] of inputs.entries()) {
19
- const localesType = input.locales.map((l) => `"${l}"`).join(" | ");
20
- const messagesType = renderInferNode(input.schemas.messagesSchema);
21
- const replacementsType = renderInferNode(input.schemas.replacementsSchema);
22
- const richType = renderInferNode(
23
- normalizeRichInferNode(input.schemas.richSchema),
24
- );
18
+ for (const [index, entry] of schemaEntries.entries()) {
19
+ const localesType = entry.locales.map((l) => `"${l}"`).join(" | ");
20
+
21
+ const messagesType = renderInferNode(entry.shapes.messages);
22
+ const replacementsType = renderInferNode(entry.shapes.replacements);
23
+ const richType = renderInferNode(normalizeRichInferNode(entry.shapes.rich));
25
24
 
26
25
  if (index === 0) {
27
26
  appendConfigBlock(lines, {
@@ -34,7 +33,7 @@ export function buildTypes(inputs: BuildInput[]): string {
34
33
  }
35
34
 
36
35
  appendConfigBlock(lines, {
37
- id: `"${input.id}"`,
36
+ id: `"${entry.id}"`,
38
37
  locales: localesType,
39
38
  messages: messagesType,
40
39
  replacements: replacementsType,
@@ -1,4 +1,4 @@
1
- import type { InferNode } from "../../../core";
1
+ import type { InferNode } from "../../../../infer-shape";
2
2
 
3
3
  /**
4
4
  * Normalize inferred rich schema before type emission.
@@ -1,4 +1,4 @@
1
- import type { InferNode } from "../../../core";
1
+ import type { InferNode } from "../../../../infer-shape";
2
2
  import { indent } from "./indent";
3
3
 
4
4
  export function renderInferNode(node: InferNode, indentLevel = 4): string {
@@ -0,0 +1,2 @@
1
+ export { buildTypes } from "./build";
2
+ export { writeTypes } from "./write-types";
@@ -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(generatedTypes: string): Promise<string> {
6
+ const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME);
7
+ return await ensureAndWrite(filePath, generatedTypes);
8
+ }
@@ -0,0 +1,20 @@
1
+ import type { InferredShapes } from "../infer-shape";
2
+
3
+ /**
4
+ * Versioned schema artifact generated from inferred semantic shapes.
5
+ */
6
+ export interface GeneratedSchema {
7
+ version: number;
8
+ toolVersion?: string;
9
+ generatedAt: string;
10
+ entries: SchemaEntry[];
11
+ }
12
+
13
+ /**
14
+ * A schema entry representing the inferred result of a single Intor config.
15
+ */
16
+ export interface SchemaEntry {
17
+ id: string;
18
+ locales: readonly string[];
19
+ shapes: InferredShapes;
20
+ }
@@ -1,11 +1,10 @@
1
- import type { ExtraExt } from "../constants";
1
+ import type { ReaderOptions } from "./types";
2
2
  import type { IntorResolvedConfig, MessageObject } from "intor";
3
3
  import { collectRuntimeMessages } from "./collect-runtime-messages";
4
4
 
5
5
  export async function collectOtherLocaleMessages(
6
6
  config: IntorResolvedConfig,
7
- exts: Array<ExtraExt> = [],
8
- customReaders?: Record<string, string>,
7
+ { exts, customReaders }: ReaderOptions,
9
8
  ): Promise<Record<string, MessageObject>> {
10
9
  const { supportedLocales, defaultLocale } = config;
11
10
 
@@ -14,12 +13,11 @@ export async function collectOtherLocaleMessages(
14
13
  for (const locale of supportedLocales) {
15
14
  if (locale === defaultLocale) continue;
16
15
 
17
- const { messages } = await collectRuntimeMessages(
18
- config,
19
- locale,
16
+ const { messages } = await collectRuntimeMessages(config, locale, {
20
17
  exts,
21
18
  customReaders,
22
- );
19
+ });
20
+
23
21
  result[locale] = messages[locale];
24
22
  }
25
23
 
@@ -1,5 +1,8 @@
1
- import type { CollectRuntimeMessagesResult, MergeOverrides } from "./types";
2
- import type { ExtraExt } from "../../core";
1
+ import type {
2
+ ReaderOptions,
3
+ CollectRuntimeMessagesResult,
4
+ MergeOverrides,
5
+ } from "./types";
3
6
  import {
4
7
  mergeMessages,
5
8
  resolveLoaderOptions,
@@ -19,8 +22,7 @@ import { resolveMessagesReader } from "./resolve-messages-reader";
19
22
  export async function collectRuntimeMessages(
20
23
  config: IntorResolvedConfig,
21
24
  locale: string,
22
- exts: Array<ExtraExt> = [],
23
- customReaders?: Record<string, string>, // {ext, customReaderFilePath}
25
+ { exts = [], customReaders }: ReaderOptions,
24
26
  ): Promise<CollectRuntimeMessagesResult> {
25
27
  // ----------------------------------------------------------------------
26
28
  // Resolve readers
@@ -74,7 +76,7 @@ export async function collectRuntimeMessages(
74
76
  onEvent: (event) => {
75
77
  overrides.push({
76
78
  ...event,
77
- layer: "client_over_server",
79
+ layer: "clientOverServer",
78
80
  locale,
79
81
  configId: config.id,
80
82
  });
@@ -88,7 +90,7 @@ export async function collectRuntimeMessages(
88
90
  onEvent: (event) => {
89
91
  overrides.push({
90
92
  ...event,
91
- layer: "runtime_over_static",
93
+ layer: "runtimeOverStatic",
92
94
  locale,
93
95
  configId: config.id,
94
96
  });
@@ -1,2 +1,3 @@
1
1
  export { collectRuntimeMessages } from "./collect-runtime-messages";
2
2
  export { collectOtherLocaleMessages } from "./collect-other-locale-messages";
3
+ export type { ReaderOptions, MergeOverrides } from "./types";
@@ -22,6 +22,7 @@ export const getBuiltInReaders = (
22
22
  return reader ? [ext, reader] : null;
23
23
  })
24
24
  .filter((e): e is [ExtraExt, MessagesReader] => e !== null);
25
+
25
26
  // Entries to object
26
27
  return Object.fromEntries(entries);
27
28
  };
@@ -1,12 +1,18 @@
1
+ import type { ExtraExt } from "../constants";
1
2
  import type { DeepMergeOverrideEvent, LocaleMessages } from "intor";
2
3
 
4
+ export interface ReaderOptions {
5
+ exts?: Array<ExtraExt>;
6
+ customReaders?: Record<string, string>; // {ext, customReaderFilePath}
7
+ }
8
+
3
9
  export interface CollectRuntimeMessagesResult {
4
10
  messages: LocaleMessages;
5
11
  overrides: MergeOverrides[];
6
12
  }
7
13
 
8
14
  export interface MergeOverrides extends DeepMergeOverrideEvent {
9
- layer: "client_over_server" | "runtime_over_static";
15
+ layer: "clientOverServer" | "runtimeOverStatic";
10
16
  locale: string;
11
17
  configId: string;
12
18
  }
@@ -1,3 +1,4 @@
1
+ // artifacts
1
2
  export {
2
3
  DEFAULT_OUT_DIR,
3
4
  DEFAULT_TYPES_FILE_NAME,
@@ -5,4 +6,5 @@ export {
5
6
  DEFAULT_MESSAGES_SNAPSHOT_DIR,
6
7
  } from "./generated";
7
8
 
9
+ // extra-exts
8
10
  export { EXTRA_EXTS, type ExtraExt } from "./extra-exts";