intor-cli 0.0.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.
Files changed (92) hide show
  1. package/README.md +46 -0
  2. package/package.json +73 -0
  3. package/src/build/build-schemas/build-schemas.ts +13 -0
  4. package/src/build/build-schemas/index.ts +1 -0
  5. package/src/build/build-types/build-types.ts +46 -0
  6. package/src/build/build-types/index.ts +1 -0
  7. package/src/build/build-types/output/append-config-block.ts +34 -0
  8. package/src/build/build-types/output/append-footer.ts +5 -0
  9. package/src/build/build-types/output/append-header.ts +11 -0
  10. package/src/build/build-types/output/index.ts +3 -0
  11. package/src/build/build-types/utils/indent.ts +3 -0
  12. package/src/build/build-types/utils/render-infer-node.ts +29 -0
  13. package/src/build/index.ts +3 -0
  14. package/src/build/types.ts +19 -0
  15. package/src/cli/commands/check.ts +50 -0
  16. package/src/cli/commands/generate.ts +61 -0
  17. package/src/cli/index.ts +24 -0
  18. package/src/core/collect-messages/collect-runtime-messages.ts +76 -0
  19. package/src/core/collect-messages/index.ts +1 -0
  20. package/src/core/collect-messages/readers.ts +23 -0
  21. package/src/core/collect-messages/resolve-messages-reader.ts +57 -0
  22. package/src/core/constants/extra-exts.ts +2 -0
  23. package/src/core/constants/generated-files.ts +3 -0
  24. package/src/core/constants/index.ts +7 -0
  25. package/src/core/diagnostics/collect.ts +59 -0
  26. package/src/core/diagnostics/group.ts +41 -0
  27. package/src/core/diagnostics/index.ts +2 -0
  28. package/src/core/diagnostics/messages.ts +51 -0
  29. package/src/core/diagnostics/rules/enforce-missing-replacements.ts +55 -0
  30. package/src/core/diagnostics/rules/enforce-missing-rich.ts +55 -0
  31. package/src/core/diagnostics/rules/key/empty.ts +34 -0
  32. package/src/core/diagnostics/rules/key/index.ts +2 -0
  33. package/src/core/diagnostics/rules/key/not-found.ts +43 -0
  34. package/src/core/diagnostics/rules/replacement/index.ts +3 -0
  35. package/src/core/diagnostics/rules/replacement/missing.ts +48 -0
  36. package/src/core/diagnostics/rules/replacement/not-allowed.ts +43 -0
  37. package/src/core/diagnostics/rules/replacement/unused.ts +48 -0
  38. package/src/core/diagnostics/rules/rich/index.ts +3 -0
  39. package/src/core/diagnostics/rules/rich/missing.ts +48 -0
  40. package/src/core/diagnostics/rules/rich/not-allowed.ts +43 -0
  41. package/src/core/diagnostics/rules/rich/unused.ts +48 -0
  42. package/src/core/diagnostics/types.ts +21 -0
  43. package/src/core/diagnostics/utils/get-schema-node-at-path.ts +29 -0
  44. package/src/core/diagnostics/utils/index-usages-by-key.ts +28 -0
  45. package/src/core/diagnostics/utils/resolve-key-path.ts +5 -0
  46. package/src/core/discover-configs/discover-configs.ts +87 -0
  47. package/src/core/discover-configs/index.ts +1 -0
  48. package/src/core/discover-configs/is-intor-resolved-config.ts +15 -0
  49. package/src/core/extract-usages/README.md +84 -0
  50. package/src/core/extract-usages/collectors/collect-key-usages.ts +40 -0
  51. package/src/core/extract-usages/collectors/collect-pre-keys.ts +58 -0
  52. package/src/core/extract-usages/collectors/collect-replacement-usages.ts +68 -0
  53. package/src/core/extract-usages/collectors/collect-rich-usages.ts +57 -0
  54. package/src/core/extract-usages/collectors/collect-translator-bindings.ts +56 -0
  55. package/src/core/extract-usages/collectors/index.ts +5 -0
  56. package/src/core/extract-usages/collectors/utils/extract-static-object-keys.ts +31 -0
  57. package/src/core/extract-usages/collectors/utils/get-config-key.ts +15 -0
  58. package/src/core/extract-usages/collectors/utils/get-object-arg.ts +42 -0
  59. package/src/core/extract-usages/collectors/utils/is-static-string-literal.ts +26 -0
  60. package/src/core/extract-usages/collectors/utils/walk-translator-bindings.ts +47 -0
  61. package/src/core/extract-usages/collectors/utils/walk-translator-method-calls.ts +42 -0
  62. package/src/core/extract-usages/extract-usages.ts +88 -0
  63. package/src/core/extract-usages/index.ts +13 -0
  64. package/src/core/extract-usages/load-source-files-from-tscofnig.ts +76 -0
  65. package/src/core/extract-usages/translator-registry.ts +20 -0
  66. package/src/core/extract-usages/types.ts +53 -0
  67. package/src/core/index.ts +13 -0
  68. package/src/core/infer-schema/index.ts +3 -0
  69. package/src/core/infer-schema/infer-schemas.ts +16 -0
  70. package/src/core/infer-schema/messages/index.ts +1 -0
  71. package/src/core/infer-schema/messages/infer-messages-schema.ts +64 -0
  72. package/src/core/infer-schema/replacements/extract-interpolation-names.ts +70 -0
  73. package/src/core/infer-schema/replacements/index.ts +1 -0
  74. package/src/core/infer-schema/replacements/infer-replacements-schema.ts +63 -0
  75. package/src/core/infer-schema/rich/index.ts +1 -0
  76. package/src/core/infer-schema/rich/infer-rich-schema.ts +66 -0
  77. package/src/core/infer-schema/types.ts +42 -0
  78. package/src/core/infer-schema/utils/infer-object.ts +32 -0
  79. package/src/core/infer-schema/utils/is-message-object.ts +5 -0
  80. package/src/core/read-generated-schema.ts +42 -0
  81. package/src/core/scan-logger.ts +10 -0
  82. package/src/core/write-generated-files.ts +51 -0
  83. package/src/features/check/check.ts +75 -0
  84. package/src/features/check/index.ts +1 -0
  85. package/src/features/check/print-summary.ts +28 -0
  86. package/src/features/generate/generate.ts +76 -0
  87. package/src/features/generate/index.ts +1 -0
  88. package/src/features/generate/print-configs.ts +8 -0
  89. package/src/features/generate/print-summary.ts +19 -0
  90. package/src/features/index.ts +2 -0
  91. package/src/features/print-title.ts +7 -0
  92. package/src/features/spinner.ts +3 -0
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ ### Design Guarantees
2
+
3
+ - Message types are inferred from the **_default locale_** only.
4
+ - All locales are assumed to share the same message shape.
5
+ - Locale is treated as a runtime dimension, not a structural one.
6
+ - Generated types are intentionally conservative and do not validate locale completeness.
7
+
8
+ ### NOTE
9
+
10
+ ```
11
+ (Core Modules)
12
+ ├── collect-messages
13
+ ├── extract-usages
14
+ ├── find-configs
15
+ ├── generate-types
16
+ ├── infer-schema
17
+ ├── validation
18
+ ```
19
+
20
+ - pipeline: Collect messages
21
+
22
+ ```
23
+ input: none (Detect from environment)
24
+ |-> find-configs -> collect-messages
25
+ ```
26
+
27
+ - pipeline: Generate intor schema
28
+
29
+ ```
30
+ intput: collected messages
31
+ |-> infer-schema (write file)
32
+ ```
33
+
34
+ - pipeline: Generate intor types
35
+
36
+ ```
37
+ intput: intor schema
38
+ |-> generate-types (write file)
39
+ ```
40
+
41
+ - pipeline: Validation
42
+
43
+ ```
44
+ intput: intor schema
45
+ |-> extract-usages -> validation
46
+ ```
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "intor-cli",
3
+ "version": "0.0.1",
4
+ "description": "Intor CLI",
5
+ "author": "Yiming Liao",
6
+ "homepage": "https://github.com/yiming-liao/intor-cli#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/yiming-liao/intor-cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/yiming-liao/intor-cli/issues"
13
+ },
14
+ "keywords": [
15
+ "i18n"
16
+ ],
17
+ "license": "MIT",
18
+ "type": "module",
19
+ "files": [
20
+ "README.md",
21
+ "LICENSE",
22
+ "src"
23
+ ],
24
+ "engines": {
25
+ "node": ">=16.0.0"
26
+ },
27
+ "scripts": {
28
+ "intor": "tsx src/cli/index.ts",
29
+ "type": "tsc --noEmit",
30
+ "lint": "eslint",
31
+ "lint:debug": "eslint --debug",
32
+ "test": "vitest",
33
+ "test:check": "tsx scripts/check-test-structure.ts",
34
+ "knip": "knip --config .config/knip.config.ts"
35
+ },
36
+ "bin": {
37
+ "intor": "src/cli/index.ts"
38
+ },
39
+ "dependencies": {
40
+ "@intor/reader-md": "0.1.1",
41
+ "@intor/reader-yaml": "0.1.0",
42
+ "cac": "6.7.14",
43
+ "fast-glob": "3.3.3",
44
+ "intor": "2.3.17",
45
+ "logry": "2.1.6",
46
+ "ora": "9.0.0",
47
+ "picocolors": "1.1.1",
48
+ "ts-morph": "27.0.2",
49
+ "tsx": "4.21.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "25.0.3",
53
+ "@vitest/coverage-v8": "4.0.16",
54
+ "eslint": "9.39.2",
55
+ "eslint-config-prettier": "10.1.8",
56
+ "eslint-import-resolver-typescript": "4.4.4",
57
+ "eslint-plugin-import": "2.32.0",
58
+ "eslint-plugin-prettier": "5.5.4",
59
+ "eslint-plugin-unicorn": "62.0.0",
60
+ "eslint-plugin-unused-imports": "4.3.0",
61
+ "knip": "5.79.0",
62
+ "prettier": "3.7.4",
63
+ "remark": "15.0.1",
64
+ "remark-gfm": "4.0.1",
65
+ "typescript": "5.9.3",
66
+ "typescript-eslint": "8.51.0",
67
+ "vitest": "4.0.16"
68
+ },
69
+ "peerDependencies": {},
70
+ "publishConfig": {
71
+ "access": "public"
72
+ }
73
+ }
@@ -0,0 +1,13 @@
1
+ import type { BuildInput, GeneratedSchema } from "../types";
2
+
3
+ export function buildSchemas(inputs: BuildInput[]): GeneratedSchema {
4
+ return {
5
+ version: 1,
6
+ generatedAt: new Date().toISOString(),
7
+ configs: inputs.map((input) => ({
8
+ id: input.id,
9
+ locales: input.locales,
10
+ schemas: input.schemas,
11
+ })),
12
+ };
13
+ }
@@ -0,0 +1 @@
1
+ export { buildSchemas } from "./build-schemas";
@@ -0,0 +1,46 @@
1
+ import type { BuildInput } from "../types";
2
+ import { appendHeader, appendConfigBlock, appendFooter } from "./output";
3
+ import { renderInferNode } from "./utils/render-infer-node";
4
+
5
+ const GENERATED_INTERFACE_NAME = "IntorGeneratedTypes";
6
+
7
+ /**
8
+ * Builds the global TypeScript declaration from inferred typegen inputs.
9
+ */
10
+ export function buildTypes(inputs: BuildInput[]): string {
11
+ const lines: string[] = [];
12
+
13
+ // Global declaration header
14
+ appendHeader(lines, GENERATED_INTERFACE_NAME);
15
+
16
+ // Per-config processing
17
+ for (const [index, input] of inputs.entries()) {
18
+ const localesType = input.locales.map((l) => `"${l}"`).join(" | ");
19
+ const messagesType = renderInferNode(input.schemas.messagesSchema);
20
+ const replacementsType = renderInferNode(input.schemas.replacementsSchema);
21
+ const richType = renderInferNode(input.schemas.richSchema);
22
+
23
+ if (index === 0) {
24
+ appendConfigBlock(lines, {
25
+ id: "__default__",
26
+ locales: localesType,
27
+ messages: messagesType,
28
+ replacements: replacementsType,
29
+ rich: richType,
30
+ });
31
+ }
32
+
33
+ appendConfigBlock(lines, {
34
+ id: `"${input.id}"`,
35
+ locales: localesType,
36
+ messages: messagesType,
37
+ replacements: replacementsType,
38
+ rich: richType,
39
+ });
40
+ }
41
+
42
+ // Global declaration footer
43
+ appendFooter(lines);
44
+
45
+ return lines.join("\n");
46
+ }
@@ -0,0 +1 @@
1
+ export { buildTypes } from "./build-types";
@@ -0,0 +1,34 @@
1
+ import { PREFIX_PLACEHOLDER } from "intor";
2
+ import { indent } from "../utils/indent";
3
+
4
+ function wrapWithLocale(type: string) {
5
+ return `{
6
+ ${indent(4)}"${PREFIX_PLACEHOLDER}": ${type};
7
+ ${indent(3)}}`;
8
+ }
9
+
10
+ export function appendConfigBlock(
11
+ lines: string[],
12
+ {
13
+ id,
14
+ locales,
15
+ messages,
16
+ replacements,
17
+ rich,
18
+ }: {
19
+ id: string;
20
+ locales: string;
21
+ messages: string;
22
+ replacements: string;
23
+ rich: string;
24
+ },
25
+ ) {
26
+ lines.push(
27
+ `${indent(2)}${id}: {`,
28
+ `${indent(3)}Locales: ${locales};`,
29
+ `${indent(3)}Messages: ${wrapWithLocale(messages)};`,
30
+ `${indent(3)}Replacements: ${wrapWithLocale(replacements)};`,
31
+ `${indent(3)}Rich: ${wrapWithLocale(rich)};`,
32
+ `${indent(2)}};`,
33
+ );
34
+ }
@@ -0,0 +1,5 @@
1
+ import { indent } from "../utils/indent";
2
+
3
+ export function appendFooter(lines: string[]) {
4
+ lines.push(`${indent(1)}}`, `}`, `export type __dummy = void;`);
5
+ }
@@ -0,0 +1,11 @@
1
+ import { indent } from "../utils/indent";
2
+
3
+ export const INTOR_GENERATED_KEY = "__intor_generated__";
4
+
5
+ export function appendHeader(lines: string[], interfaceName: string) {
6
+ lines.push(
7
+ `declare global {`,
8
+ `${indent(1)}interface ${interfaceName} {`,
9
+ `${indent(2)}${INTOR_GENERATED_KEY}: true;`,
10
+ );
11
+ }
@@ -0,0 +1,3 @@
1
+ export { appendHeader } from "./append-header";
2
+ export { appendConfigBlock } from "./append-config-block";
3
+ export { appendFooter } from "./append-footer";
@@ -0,0 +1,3 @@
1
+ export function indent(level: number, step = 2) {
2
+ return " ".repeat(level * step);
3
+ }
@@ -0,0 +1,29 @@
1
+ import type { InferNode } from "../../../core/infer-schema";
2
+ import { indent } from "./indent";
3
+
4
+ export function renderInferNode(node: InferNode, indentLevel = 4): string {
5
+ switch (node.kind) {
6
+ case "none": {
7
+ return "unknown";
8
+ }
9
+ case "primitive": {
10
+ return node.type;
11
+ }
12
+ case "array": {
13
+ return `${renderInferNode(node.element, indentLevel)}[]`;
14
+ }
15
+ case "record": {
16
+ return "Record<string, unknown>";
17
+ }
18
+ case "object": {
19
+ return `{
20
+ ${Object.entries(node.properties)
21
+ .map(
22
+ ([k, v]) =>
23
+ `${indent(indentLevel)} "${k}": ${renderInferNode(v, indentLevel + 1)};`,
24
+ )
25
+ .join("\n")}
26
+ ${indent(indentLevel)}}`;
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export { buildTypes } from "./build-types";
2
+ export { buildSchemas } from "./build-schemas";
3
+ export type { BuildInput, GeneratedSchema } from "./types";
@@ -0,0 +1,19 @@
1
+ import type { InferredSchemas } from "../core";
2
+
3
+ export interface BuildInput {
4
+ id: string;
5
+ locales: readonly string[];
6
+ schemas: InferredSchemas;
7
+ }
8
+
9
+ // Schema
10
+ export interface GeneratedSchema {
11
+ version: number;
12
+ generatedAt: string;
13
+ configs: GeneratedSchemaConfig[];
14
+ }
15
+ export interface GeneratedSchemaConfig {
16
+ id: string;
17
+ locales: readonly string[];
18
+ schemas: InferredSchemas;
19
+ }
@@ -0,0 +1,50 @@
1
+ import type { ReadGeneratedSchemaOptions } from "../../core";
2
+ import type { ExtractUsagesOptions } from "../../core/extract-usages/extract-usages";
3
+ import type { CAC } from "cac";
4
+ import { check } from "../../features";
5
+
6
+ export function registerCheckCommand(cli: CAC) {
7
+ cli
8
+ // -----------------------------------------------------------------------
9
+ // Command
10
+ // -----------------------------------------------------------------------
11
+ .command("check", "Validate intor translation usage")
12
+
13
+ // -----------------------------------------------------------------------
14
+ // Option
15
+ // -----------------------------------------------------------------------
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
+ .option(
31
+ "--tsconfig <path>",
32
+ "Path to tsconfig.json (default: tsconfig.json)",
33
+ )
34
+ .option("--debug", "Enable debug logging")
35
+
36
+ // -----------------------------------------------------------------------
37
+ // Action
38
+ // -----------------------------------------------------------------------
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
+ );
50
+ }
@@ -0,0 +1,61 @@
1
+ import type { ExtraExt } from "../../core";
2
+ import type { CAC } from "cac";
3
+ import { generate } from "../../features";
4
+
5
+ export function registerGenerateCommand(cli: CAC) {
6
+ cli
7
+ // -----------------------------------------------------------------------
8
+ // Command
9
+ // -----------------------------------------------------------------------
10
+ .command("generate", "Generate intor types and schemas")
11
+
12
+ // -----------------------------------------------------------------------
13
+ // Option
14
+ // -----------------------------------------------------------------------
15
+ .option(
16
+ "--ext <ext>",
17
+ "Enable extra messages file extension (repeatable)",
18
+ { default: [] },
19
+ )
20
+ .option(
21
+ "--reader <mapping>",
22
+ "Custom reader mapping in the form <ext=path> (repeatable)",
23
+ { default: [] },
24
+ )
25
+ .option(
26
+ "--debug",
27
+ "Print debug information during config discovery and generation",
28
+ )
29
+
30
+ // -----------------------------------------------------------------------
31
+ // Action
32
+ // -----------------------------------------------------------------------
33
+ .action(async (options) => {
34
+ const { ext, reader, debug } = options as {
35
+ ext?: Array<ExtraExt>;
36
+ reader?: string[];
37
+ debug?: boolean;
38
+ };
39
+
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
+ }
53
+
54
+ try {
55
+ await generate({ exts, customReaders, debug });
56
+ } catch (error) {
57
+ console.error(error);
58
+ process.exitCode = 1;
59
+ }
60
+ });
61
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { cac } from "cac";
4
+ import { registerCheckCommand } from "./commands/check";
5
+ import { registerGenerateCommand } from "./commands/generate";
6
+
7
+ const cli = cac("intor");
8
+
9
+ // ---------------------------------------------------------------------
10
+ // Register commands
11
+ // ---------------------------------------------------------------------
12
+ registerGenerateCommand(cli);
13
+ registerCheckCommand(cli);
14
+
15
+ // ---------------------------------------------------------------------
16
+ // Global options / help
17
+ // ---------------------------------------------------------------------
18
+ cli.help();
19
+ cli.version("0.1.10");
20
+
21
+ // ---------------------------------------------------------------------
22
+ // Parse argv
23
+ // ---------------------------------------------------------------------
24
+ cli.parse();
@@ -0,0 +1,76 @@
1
+ import type { ExtraExt } from "../../core";
2
+ import {
3
+ mergeMessages,
4
+ resolveLoaderOptions,
5
+ type IntorResolvedConfig,
6
+ type LocaleMessages,
7
+ type MessagesReaders,
8
+ } from "intor";
9
+ import { loadMessages } from "intor/server";
10
+ import { getBuiltInReaders } from "../collect-messages/readers";
11
+ import { resolveMessagesReader } from "./resolve-messages-reader";
12
+
13
+ /**
14
+ * Collect runtime messages for a given locale.
15
+ *
16
+ * Precedence: client-side runtime > server-side runtime > static messages.
17
+ */
18
+ export async function collectRuntimeMessages(
19
+ config: IntorResolvedConfig,
20
+ locale: string,
21
+ exts: Array<ExtraExt> = [],
22
+ customReaders?: Record<string, string>, // {ext, customReaderFilePath}
23
+ ): Promise<LocaleMessages> {
24
+ // ----------------------------------------------------------------------
25
+ // Resolve readers
26
+ // ----------------------------------------------------------------------
27
+ // Resolve optional custom readers
28
+ const resolvedCustomReaders: MessagesReaders = {};
29
+ for (const [ext, filePath] of Object.entries(customReaders || {})) {
30
+ const resolved = await resolveMessagesReader(filePath);
31
+ if (resolved) resolvedCustomReaders[ext] = resolved;
32
+ }
33
+ const readers = { ...getBuiltInReaders(exts), ...resolvedCustomReaders };
34
+
35
+ // ----------------------------------------------------------------------
36
+ // Load runtime messages
37
+ // ----------------------------------------------------------------------
38
+ let serverMessages: LocaleMessages | undefined;
39
+ let clientMessages: LocaleMessages | undefined;
40
+
41
+ // Load server-side runtime messages
42
+ if (config.loader || config.server?.loader) {
43
+ const serverLoader = resolveLoaderOptions(config, "server");
44
+ if (serverLoader) {
45
+ serverMessages = await loadMessages({
46
+ config: { ...config, loader: serverLoader },
47
+ locale,
48
+ readers,
49
+ });
50
+ }
51
+ }
52
+
53
+ // Load client-side runtime messages
54
+ if (config.client?.loader) {
55
+ const clientLoader = resolveLoaderOptions(config, "client");
56
+ if (clientLoader) {
57
+ clientMessages = await loadMessages({
58
+ config: { ...config, loader: clientLoader },
59
+ locale,
60
+ readers,
61
+ });
62
+ }
63
+ }
64
+
65
+ // ----------------------------------------------------------------------
66
+ // Merge messages
67
+ // ----------------------------------------------------------------------
68
+ // client-side runtime > server-side runtime
69
+ const runtimeMessages = mergeMessages(serverMessages, clientMessages, {
70
+ config,
71
+ locale,
72
+ });
73
+
74
+ // runtime messages > static messages
75
+ return mergeMessages(config.messages, runtimeMessages, { config, locale });
76
+ }
@@ -0,0 +1 @@
1
+ export { collectRuntimeMessages } from "./collect-runtime-messages";
@@ -0,0 +1,23 @@
1
+ import type { ExtraExt } from "../../core/constants";
2
+ import type { MessagesReader, MessagesReaders } from "intor";
3
+ import { mdReader } from "@intor/reader-md";
4
+ import { yamlReader } from "@intor/reader-yaml";
5
+
6
+ export const BUILTIN_READERS: Record<ExtraExt, MessagesReader> = {
7
+ md: mdReader,
8
+ yaml: yamlReader,
9
+ };
10
+
11
+ export const getBuiltInReaders = (
12
+ exts: readonly ExtraExt[],
13
+ ): MessagesReaders => {
14
+ // Map to [ext, reader]
15
+ const entries = exts
16
+ .map((ext) => {
17
+ const reader = BUILTIN_READERS[ext];
18
+ return reader ? [ext, reader] : null;
19
+ })
20
+ .filter((e): e is [ExtraExt, MessagesReader] => e !== null);
21
+ // Entries to object
22
+ return Object.fromEntries(entries);
23
+ };
@@ -0,0 +1,57 @@
1
+ import type { MessagesReader } from "intor";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ /**
6
+ * Resolve a MessagesReader from a module path.
7
+ *
8
+ * Resolution order:
9
+ * 1. Default export
10
+ * 2. First exported function (fallback)
11
+ */
12
+ export async function resolveMessagesReader(
13
+ filePath?: string,
14
+ ): Promise<MessagesReader | undefined> {
15
+ if (!filePath) return;
16
+
17
+ const absolutePath = path.resolve(process.cwd(), filePath);
18
+
19
+ // ----------------------------------------------------------------------
20
+ // Ensure the file exists before attempting dynamic import
21
+ // ----------------------------------------------------------------------
22
+ try {
23
+ await fs.access(absolutePath);
24
+ } catch {
25
+ throw new Error(`[intor] Reader file not found: ${filePath}`);
26
+ }
27
+
28
+ // ----------------------------------------------------------------------
29
+ // Resolve reader module exports
30
+ // ----------------------------------------------------------------------
31
+ // Dynamically import the reader module
32
+ const moduleExports = await import(absolutePath);
33
+
34
+ // Prefer default export
35
+ let reader = moduleExports.default;
36
+
37
+ // Fallback to the first exported function
38
+ if (typeof reader !== "function") {
39
+ for (const value of Object.values(moduleExports)) {
40
+ if (typeof value === "function") {
41
+ reader = value;
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ // ----------------------------------------------------------------------
48
+ // Validate resolved reader
49
+ // ----------------------------------------------------------------------
50
+ if (typeof reader !== "function") {
51
+ throw new Error(
52
+ `[intor] No function export found in reader module: ${filePath}`,
53
+ );
54
+ }
55
+
56
+ return reader;
57
+ }
@@ -0,0 +1,2 @@
1
+ export const EXTRA_EXTS = ["md", "yaml"] as const;
2
+ export type ExtraExt = (typeof EXTRA_EXTS)[number];
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_OUT_DIR = ".intor";
2
+ export const DEFAULT_TYPES_FILE = "intor-generated-types.d.ts";
3
+ export const DEFAULT_SCHEMA_FILE = "intor-generated.schema.json";
@@ -0,0 +1,7 @@
1
+ export {
2
+ DEFAULT_OUT_DIR,
3
+ DEFAULT_TYPES_FILE,
4
+ DEFAULT_SCHEMA_FILE,
5
+ } from "./generated-files";
6
+
7
+ export { EXTRA_EXTS, type ExtraExt } from "./extra-exts";