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
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Result of a semantic inference step.
3
+ *
4
+ * - none: no meaningful type could be inferred (intentionally skipped)
5
+ * - inferred: contains a concrete TypeScript type representation
6
+ */
7
+
8
+ export type InferNode =
9
+ | InferNoneNode
10
+ | InferPrimitiveNode
11
+ | InferArrayNode
12
+ | InferObjectNode
13
+ | InferRecordNode;
14
+
15
+ export interface InferNoneNode {
16
+ kind: "none";
17
+ }
18
+
19
+ export interface InferPrimitiveNode {
20
+ kind: "primitive";
21
+ type: "string" | "number" | "boolean" | "null";
22
+ }
23
+
24
+ export interface InferArrayNode {
25
+ kind: "array";
26
+ element: InferNode;
27
+ }
28
+
29
+ export interface InferObjectNode {
30
+ kind: "object";
31
+ properties: Record<string, InferNode>;
32
+ }
33
+
34
+ export interface InferRecordNode {
35
+ kind: "record";
36
+ }
37
+
38
+ export interface InferredSchemas {
39
+ messagesSchema: InferNode;
40
+ replacementsSchema: InferNode;
41
+ richSchema: InferNode;
42
+ }
@@ -0,0 +1,32 @@
1
+ import type { InferNode } from "../types";
2
+ import type { MessageObject, MessageValue } from "intor";
3
+
4
+ /**
5
+ * Infer an object-like semantic node by aggregating inferred children.
6
+ *
7
+ * - Delegates inference to child nodes
8
+ * - Prunes branches without semantic meaning
9
+ * - Returns `none` if no children remain
10
+ */
11
+ export function inferObject(
12
+ value: MessageObject,
13
+ inferChild: (value: MessageValue) => InferNode,
14
+ ): InferNode {
15
+ const properties: Record<string, InferNode> = {};
16
+
17
+ for (const [key, val] of Object.entries(value)) {
18
+ const child = inferChild(val);
19
+
20
+ // Skip branches without semantic meaning
21
+ if (child.kind === "none") continue;
22
+
23
+ properties[key] = child;
24
+ }
25
+
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 };
32
+ }
@@ -0,0 +1,5 @@
1
+ import type { MessageObject } from "intor";
2
+
3
+ export function isMessageObject(value: unknown): value is MessageObject {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
@@ -0,0 +1,42 @@
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
+ }
@@ -0,0 +1,10 @@
1
+ import pc from "picocolors";
2
+
3
+ const log = (tag: string, message: string) => {
4
+ console.log(pc.dim(` (${tag})`) + pc.gray(` ${message}`));
5
+ };
6
+
7
+ export function createLogger(enabled?: boolean) {
8
+ if (!enabled) return () => {};
9
+ return log;
10
+ }
@@ -0,0 +1,51 @@
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
+ }
@@ -0,0 +1,75 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import type { ReadGeneratedSchemaOptions } from "../../core";
3
+ import type { ExtractUsagesOptions } from "../../core/extract-usages/extract-usages";
4
+ import {
5
+ readGeneratedSchema,
6
+ extractUsages,
7
+ collectDiagnostics,
8
+ groupDiagnostics,
9
+ } from "../../core";
10
+ import { printTitle } from "../../features/print-title";
11
+ import { spinner } from "../spinner";
12
+ import { printSummary } from "./print-summary";
13
+
14
+ function resolveConfigKey(
15
+ usageConfigKey: string | undefined,
16
+ defaultConfigKey: string,
17
+ ) {
18
+ if (usageConfigKey === "__default__") return defaultConfigKey;
19
+ if (usageConfigKey == null) return defaultConfigKey;
20
+ return usageConfigKey;
21
+ }
22
+
23
+ export async function check(
24
+ readOptions?: ReadGeneratedSchemaOptions,
25
+ extractOptions?: ExtractUsagesOptions,
26
+ ) {
27
+ printTitle("Checking intor diagnostics");
28
+ spinner.start();
29
+
30
+ try {
31
+ // -----------------------------------------------------------------------
32
+ // Read generated schema
33
+ // -----------------------------------------------------------------------
34
+ const generatedSchema = await readGeneratedSchema(readOptions);
35
+
36
+ // -----------------------------------------------------------------------
37
+ // Extract usages
38
+ // -----------------------------------------------------------------------
39
+ const usages = extractUsages(extractOptions);
40
+
41
+ // Use first config's id as default key
42
+ const defaultConfigKey = generatedSchema.configs[0]?.id;
43
+
44
+ spinner.stop();
45
+
46
+ // Per-config processing
47
+ for (const config of generatedSchema.configs) {
48
+ // configKey <-> config.id
49
+ const configKey = config.id;
50
+
51
+ // per-config usages
52
+ const scopedUsages = {
53
+ keys: usages.keys.filter(
54
+ (u) => resolveConfigKey(u.configKey, defaultConfigKey) === configKey,
55
+ ),
56
+ replacements: usages.replacements.filter(
57
+ (u) => resolveConfigKey(u.configKey, defaultConfigKey) === configKey,
58
+ ),
59
+ rich: usages.rich.filter(
60
+ (u) => resolveConfigKey(u.configKey, defaultConfigKey) === configKey,
61
+ ),
62
+ };
63
+
64
+ // Diagnostic
65
+ const diagnostics = collectDiagnostics(config.schemas, scopedUsages);
66
+ const grouped = groupDiagnostics(diagnostics);
67
+
68
+ printSummary(config.id, grouped);
69
+ }
70
+ } catch (error) {
71
+ spinner.stop();
72
+ console.error(error instanceof Error ? error.message : String(error));
73
+ process.exit(1);
74
+ }
75
+ }
@@ -0,0 +1 @@
1
+ export { check } from "./check";
@@ -0,0 +1,28 @@
1
+ import type { DiagnosticGroup } from "../../core/diagnostics/types";
2
+ import pc from "picocolors";
3
+
4
+ export function printSummary(configId: string, grouped: DiagnosticGroup[]) {
5
+ // Log header
6
+ console.log(pc.dim("Config:"), pc.cyan(`${configId}\n`));
7
+
8
+ // Log no issues
9
+ if (grouped.length === 0) {
10
+ console.log(pc.dim("✔ Diagnostics completed with no issues.\n"));
11
+ return;
12
+ }
13
+
14
+ // Log problems
15
+ for (const group of grouped) {
16
+ const { method, messageKey, problems, file, lines } = group;
17
+
18
+ const header = `${messageKey} ${`(${method})`}\n`;
19
+
20
+ const problemsLine = [
21
+ ...problems.map((p) => pc.gray(` - ${p}`)),
22
+ pc.dim(` ➜ ${file}:${lines.join(",")}`),
23
+ "",
24
+ ].join("\n");
25
+
26
+ console.log(header + problemsLine);
27
+ }
28
+ }
@@ -0,0 +1,76 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import type { ExtraExt } from "../../core";
3
+ import { buildTypes, buildSchemas, type BuildInput } from "../../build";
4
+ import {
5
+ discoverConfigs,
6
+ collectRuntimeMessages,
7
+ inferSchemas,
8
+ writeGeneratedFiles,
9
+ } from "../../core";
10
+ import { printTitle } from "../print-title";
11
+ import { spinner } from "../spinner";
12
+ import { printConfigs } from "./print-configs";
13
+ import { printSummary } from "./print-summary";
14
+
15
+ export interface GenerateOptions {
16
+ exts?: Array<ExtraExt>;
17
+ customReaders?: Record<string, string>;
18
+ debug?: boolean;
19
+ }
20
+
21
+ export async function generate({
22
+ exts = [],
23
+ customReaders,
24
+ debug,
25
+ }: GenerateOptions) {
26
+ printTitle("Generating intor types");
27
+ spinner.start();
28
+ const start = performance.now();
29
+
30
+ try {
31
+ // -----------------------------------------------------------------------
32
+ // Discover configs from the current workspace
33
+ // -----------------------------------------------------------------------
34
+ const configEntries = await discoverConfigs(debug);
35
+ if (configEntries.length === 0) {
36
+ spinner.stop();
37
+ throw new Error("No Intor config found for type generation.");
38
+ }
39
+
40
+ // -----------------------------------------------------------------------
41
+ // Collect messages and infer schemas
42
+ // -----------------------------------------------------------------------
43
+ const buildInputs: BuildInput[] = [];
44
+
45
+ // Runtime mode - Per-config processing
46
+ for (const { config, filePath } of configEntries) {
47
+ const { id, supportedLocales } = config;
48
+ printConfigs(id, filePath);
49
+ const messages = await collectRuntimeMessages(
50
+ config,
51
+ config.defaultLocale,
52
+ exts,
53
+ customReaders,
54
+ );
55
+ const schemas = inferSchemas(messages[config.defaultLocale]);
56
+ buildInputs.push({ id, locales: supportedLocales, schemas });
57
+ }
58
+
59
+ // -----------------------------------------------------------------------
60
+ // Build artifacts and write output
61
+ // -----------------------------------------------------------------------
62
+ const types = buildTypes(buildInputs);
63
+ const schema = buildSchemas(buildInputs);
64
+
65
+ // Write generated files
66
+ const { outDir } = await writeGeneratedFiles({ types, schema });
67
+
68
+ spinner.stop();
69
+ const duration = performance.now() - start;
70
+ printSummary(outDir, duration);
71
+ } catch (error) {
72
+ spinner.stop();
73
+ console.error(error instanceof Error ? error.message : String(error));
74
+ process.exit(1);
75
+ }
76
+ }
@@ -0,0 +1 @@
1
+ export { generate } from "./generate";
@@ -0,0 +1,8 @@
1
+ import pc from "picocolors";
2
+ import { spinner } from "../spinner";
3
+
4
+ export function printConfigs(id: string, filePath: string) {
5
+ spinner.stop();
6
+ console.log(pc.dim("- Config ") + pc.cyan(id) + pc.dim(` ${filePath}`));
7
+ spinner.start();
8
+ }
@@ -0,0 +1,19 @@
1
+ import pc from "picocolors";
2
+
3
+ export function printSummary(outDir: string, ms: number) {
4
+ const labelWidth = 18;
5
+ const label = (text: string) => pc.dim(text.padEnd(labelWidth));
6
+
7
+ console.log();
8
+ console.log(pc.green(pc.bold("✔ intor generate completed")));
9
+ console.log();
10
+ console.log(label("Output directory: ") + pc.gray(outDir));
11
+ console.log(label("Time elapsed: ") + pc.gray(`${(ms / 1000).toFixed(2)}s`));
12
+ console.log();
13
+ console.log(
14
+ pc.dim("💡 Remember to include ") +
15
+ pc.gray(".intor/**/*.d.ts") +
16
+ pc.dim(" in your tsconfig.json "),
17
+ );
18
+ console.log();
19
+ }
@@ -0,0 +1,2 @@
1
+ export { generate } from "./generate";
2
+ export { check } from "./check";
@@ -0,0 +1,7 @@
1
+ import pc from "picocolors";
2
+
3
+ export function printTitle(title: string) {
4
+ console.log();
5
+ console.log(pc.bgBlack(` • ${title} `));
6
+ console.log();
7
+ }
@@ -0,0 +1,3 @@
1
+ import ora from "ora";
2
+
3
+ export const spinner = ora({ isEnabled: process.stdout.isTTY });