intor-cli 0.0.9 → 0.0.11

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 (78) 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/build-types.ts +4 -1
  5. package/src/build/build-types/utils/normalize-rich-infer-node.ts +40 -0
  6. package/src/build/build-types/utils/render-infer-node.ts +4 -2
  7. package/src/build/index.ts +1 -1
  8. package/src/build/types.ts +4 -3
  9. package/src/cli/commands/check.ts +9 -26
  10. package/src/cli/commands/generate.ts +2 -13
  11. package/src/cli/commands/utils/normalize-reader-options.ts +46 -0
  12. package/src/cli/commands/validate.ts +50 -0
  13. package/src/cli/index.ts +2 -0
  14. package/src/core/collect-messages/collect-other-locale-messages.ts +27 -0
  15. package/src/core/collect-messages/index.ts +1 -0
  16. package/src/core/constants/generated.ts +5 -0
  17. package/src/core/constants/index.ts +4 -3
  18. package/src/core/extract-usages/index.ts +1 -1
  19. package/src/core/generated/ensure-and-write.ts +11 -0
  20. package/src/core/generated/index.ts +6 -0
  21. package/src/core/generated/read-schema.ts +33 -0
  22. package/src/core/generated/write-messages-snapshot.ts +27 -0
  23. package/src/core/generated/write-schema.ts +9 -0
  24. package/src/core/generated/write-types.ts +8 -0
  25. package/src/core/index.ts +36 -9
  26. package/src/core/infer-schema/index.ts +2 -1
  27. package/src/core/infer-schema/infer-schemas.ts +5 -1
  28. package/src/core/infer-schema/messages/infer-messages-schema.ts +1 -1
  29. package/src/core/infer-schema/replacements/infer-replacements-schema.ts +1 -1
  30. package/src/core/infer-schema/rich/infer-rich-schema.ts +1 -1
  31. package/src/core/infer-schema/utils/infer-object.ts +8 -6
  32. package/src/core/infer-schema/utils/should-skip-key.ts +15 -0
  33. package/src/core/infer-schema/utils/strip-internal-keys.ts +30 -0
  34. package/src/features/check/check.ts +7 -15
  35. package/src/{core → features/check}/diagnostics/collect.ts +1 -2
  36. package/src/{core → features/check}/diagnostics/rules/enforce-missing-replacements.ts +1 -2
  37. package/src/{core → features/check}/diagnostics/rules/enforce-missing-rich.ts +1 -2
  38. package/src/{core → features/check}/diagnostics/rules/key/empty.ts +1 -1
  39. package/src/{core → features/check}/diagnostics/rules/key/not-found.ts +1 -2
  40. package/src/{core → features/check}/diagnostics/rules/pre-key/not-found.ts +1 -2
  41. package/src/{core → features/check}/diagnostics/rules/replacement/missing.ts +1 -2
  42. package/src/{core → features/check}/diagnostics/rules/replacement/not-allowed.ts +1 -2
  43. package/src/{core → features/check}/diagnostics/rules/replacement/unused.ts +1 -2
  44. package/src/{core → features/check}/diagnostics/rules/rich/missing.ts +1 -2
  45. package/src/{core → features/check}/diagnostics/rules/rich/not-allowed.ts +1 -2
  46. package/src/{core → features/check}/diagnostics/rules/rich/unused.ts +1 -2
  47. package/src/{core → features/check}/diagnostics/types.ts +1 -1
  48. package/src/{core → features/check}/diagnostics/utils/get-schema-node-at-path.ts +1 -1
  49. package/src/{core → features/check}/diagnostics/utils/index-usages-by-key.ts +1 -1
  50. package/src/features/check/print-summary.ts +15 -15
  51. package/src/features/generate/generate.ts +16 -8
  52. package/src/features/generate/print-configs.ts +3 -3
  53. package/src/features/generate/print-overrides.ts +44 -26
  54. package/src/features/generate/print-summary.ts +21 -13
  55. package/src/features/index.ts +1 -0
  56. package/src/features/print.ts +43 -0
  57. package/src/features/validate/index.ts +1 -0
  58. package/src/features/validate/messages/index.ts +1 -0
  59. package/src/features/validate/messages/validate-messages-schema.ts +36 -0
  60. package/src/features/validate/print-summary.ts +65 -0
  61. package/src/features/validate/replacements/index.ts +1 -0
  62. package/src/features/validate/replacements/validate-replacements-schema.ts +51 -0
  63. package/src/features/validate/rich/index.ts +1 -0
  64. package/src/features/validate/rich/validate-rich-schema.ts +48 -0
  65. package/src/features/validate/validate-locale-messages.ts +38 -0
  66. package/src/features/validate/validate.ts +84 -0
  67. package/src/core/constants/generated-files.ts +0 -3
  68. package/src/core/read-generated-schema.ts +0 -42
  69. package/src/core/write-generated-files.ts +0 -51
  70. package/src/features/print-title.ts +0 -7
  71. /package/src/{core → features/check}/diagnostics/group.ts +0 -0
  72. /package/src/{core → features/check}/diagnostics/index.ts +0 -0
  73. /package/src/{core → features/check}/diagnostics/messages.ts +0 -0
  74. /package/src/{core → features/check}/diagnostics/rules/key/index.ts +0 -0
  75. /package/src/{core → features/check}/diagnostics/rules/pre-key/index.ts +0 -0
  76. /package/src/{core → features/check}/diagnostics/rules/replacement/index.ts +0 -0
  77. /package/src/{core → features/check}/diagnostics/rules/rich/index.ts +0 -0
  78. /package/src/{core → features/check}/diagnostics/utils/resolve-key-path.ts +0 -0
@@ -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
+ }
@@ -0,0 +1,30 @@
1
+ import { INTOR_PREFIX } from "intor";
2
+
3
+ /**
4
+ * Remove internal Intor keys from inferred schema objects.
5
+ *
6
+ * This runs after schema inference to avoid leaking
7
+ * internal metadata into generated types.
8
+ */
9
+ export function stripInternalKeys(target: unknown): void {
10
+ if (!target || typeof target !== "object") return;
11
+
12
+ // Handle arrays explicitly
13
+ if (Array.isArray(target)) {
14
+ for (const item of target) {
15
+ stripInternalKeys(item);
16
+ }
17
+ return;
18
+ }
19
+
20
+ const record = target as Record<string, unknown>;
21
+
22
+ for (const key of Object.keys(record)) {
23
+ if (key.startsWith(INTOR_PREFIX)) {
24
+ delete record[key];
25
+ continue;
26
+ }
27
+
28
+ stripInternalKeys(record[key]);
29
+ }
30
+ }
@@ -1,15 +1,10 @@
1
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";
2
+ import type { ExtractUsagesOptions } from "../../core";
3
+ import { extractUsages, readSchema } from "../../core";
4
+ import { printTitle } from "../print";
11
5
  import { spinner } from "../spinner";
12
6
  import { dedupePreKeyUsages } from "./dedupe-pre-key-usages";
7
+ import { collectDiagnostics, groupDiagnostics } from "./diagnostics";
13
8
  import { printSummary } from "./print-summary";
14
9
 
15
10
  function resolveConfigKey(
@@ -21,18 +16,15 @@ function resolveConfigKey(
21
16
  return usageConfigKey;
22
17
  }
23
18
 
24
- export async function check(
25
- readOptions?: ReadGeneratedSchemaOptions,
26
- extractOptions?: ExtractUsagesOptions,
27
- ) {
28
- printTitle("Checking intor diagnostics");
19
+ export async function check(extractOptions?: ExtractUsagesOptions) {
20
+ printTitle("Checking intor usages");
29
21
  spinner.start();
30
22
 
31
23
  try {
32
24
  // -----------------------------------------------------------------------
33
25
  // Read generated schema
34
26
  // -----------------------------------------------------------------------
35
- const generatedSchema = await readGeneratedSchema(readOptions);
27
+ const generatedSchema = await readSchema();
36
28
 
37
29
  // -----------------------------------------------------------------------
38
30
  // Extract usages
@@ -1,6 +1,5 @@
1
1
  import type { Diagnostic } from "./types";
2
- import type { ExtractedUsages } from "../extract-usages";
3
- import type { InferredSchemas } from "../infer-schema/types";
2
+ import type { ExtractedUsages, InferredSchemas } from "../../../core";
4
3
  import { enforceMissingReplacements } from "./rules/enforce-missing-replacements";
5
4
  import { enforceMissingRich } from "./rules/enforce-missing-rich";
6
5
  import { keyEmpty, keyNotFound } from "./rules/key";
@@ -1,5 +1,4 @@
1
- import type { KeyUsage, ReplacementUsage } from "../../extract-usages";
2
- import type { InferNode } from "../../infer-schema";
1
+ import type { KeyUsage, ReplacementUsage, InferNode } from "../../../../core";
3
2
  import type { Diagnostic } from "../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../messages";
5
4
  import { getSchemaNodeAtPath } from "../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { KeyUsage, RichUsage } from "../../extract-usages";
2
- import type { InferNode } from "../../infer-schema";
1
+ import type { KeyUsage, RichUsage, InferNode } from "../../../../core";
3
2
  import type { Diagnostic } from "../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../messages";
5
4
  import { getSchemaNodeAtPath } from "../utils/get-schema-node-at-path";
@@ -1,4 +1,4 @@
1
- import type { KeyUsage } from "../../../extract-usages";
1
+ import type { KeyUsage } from "../../../../../core";
2
2
  import type { Diagnostic } from "../../types";
3
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
4
4
 
@@ -1,5 +1,4 @@
1
- import type { KeyUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { KeyUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { PreKeyUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { PreKeyUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { ReplacementUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { ReplacementUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { ReplacementUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { ReplacementUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { ReplacementUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { ReplacementUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { RichUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { RichUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { RichUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { RichUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,5 +1,4 @@
1
- import type { RichUsage } from "../../../extract-usages";
2
- import type { InferNode } from "../../../infer-schema";
1
+ import type { RichUsage, InferNode } from "../../../../../core";
3
2
  import type { Diagnostic } from "../../types";
4
3
  import { DIAGNOSTIC_MESSAGES } from "../../messages";
5
4
  import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
@@ -1,4 +1,4 @@
1
- import type { TranslatorFactory, TranslatorMethod } from "../extract-usages";
1
+ import type { TranslatorFactory, TranslatorMethod } from "../../../core";
2
2
 
3
3
  export interface Diagnostic {
4
4
  severity: "error" | "warn";
@@ -1,4 +1,4 @@
1
- import type { InferNode } from "../../infer-schema";
1
+ import type { InferNode } from "../../../../core";
2
2
 
3
3
  /**
4
4
  * Resolve a dot-separated key path to a schema node.
@@ -1,4 +1,4 @@
1
- import type { TranslatorMethod } from "../../extract-usages";
1
+ import type { TranslatorMethod } from "../../../../core";
2
2
  import { resolveKeyPath } from "./resolve-key-path";
3
3
 
4
4
  /**
@@ -1,28 +1,28 @@
1
- import type { DiagnosticGroup } from "../../core/diagnostics/types";
2
- import pc from "picocolors";
1
+ import type { DiagnosticGroup } from "./diagnostics/types";
2
+ import { br, dim, printList, cyan, print, gray } from "../print";
3
3
 
4
4
  export function printSummary(configId: string, grouped: DiagnosticGroup[]) {
5
- // Log header
6
- console.log(pc.dim("Config:"), pc.cyan(`${configId}\n`));
5
+ print(`${dim("Config:")} ${cyan(configId)}`);
6
+ br();
7
7
 
8
- // Log no issues
8
+ // No issues
9
9
  if (grouped.length === 0) {
10
- console.log(pc.dim("✔ Diagnostics completed with no issues.\n"));
10
+ print(dim("✔ All usages are valid\n"), 1);
11
11
  return;
12
12
  }
13
13
 
14
- // Log problems
14
+ // Problems
15
15
  for (const group of grouped) {
16
16
  const { factory, method, messageKey, problems, file, lines } = group;
17
17
 
18
- const header = `${messageKey} ${`(${method ?? factory})`}\n`;
18
+ const header = `${messageKey} (${method ?? factory})`;
19
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);
20
+ print(header, 1);
21
+ printList(
22
+ null,
23
+ problems.map((p) => gray(p)),
24
+ 1,
25
+ );
26
+ print(dim(` ⚲ ${file}:${lines.join(",")}\n`), 1);
27
27
  }
28
28
  }
@@ -1,15 +1,17 @@
1
1
  /* eslint-disable unicorn/no-process-exit */
2
2
  import type { ExtraExt } from "../../core";
3
+ import path from "node:path";
3
4
  import { buildTypes, buildSchemas, type BuildInput } from "../../build";
4
5
  import {
5
6
  discoverConfigs,
6
7
  collectRuntimeMessages,
7
8
  inferSchemas,
8
- writeGeneratedFiles,
9
+ writeTypes,
10
+ writeSchema,
11
+ DEFAULT_OUT_DIR,
9
12
  } from "../../core";
10
- import { printTitle } from "../print-title";
13
+ import { cyan, dim, print, printTitle } from "../print";
11
14
  import { spinner } from "../spinner";
12
- import { printConfigs } from "./print-configs";
13
15
  import { printOverrides } from "./print-overrides";
14
16
  import { printSummary } from "./print-summary";
15
17
 
@@ -35,7 +37,7 @@ export async function generate({
35
37
  const configEntries = await discoverConfigs(debug);
36
38
  if (configEntries.length === 0) {
37
39
  spinner.stop();
38
- throw new Error("No Intor config found for type generation.");
40
+ throw new Error("No Intor config found.");
39
41
  }
40
42
 
41
43
  // -----------------------------------------------------------------------
@@ -46,13 +48,18 @@ export async function generate({
46
48
  // Runtime mode - Per-config processing
47
49
  for (const { config, filePath } of configEntries) {
48
50
  const { id, supportedLocales: locales } = config;
49
- printConfigs(id, filePath);
51
+
52
+ spinner.stop();
53
+ print(`${dim("Config:")} ${cyan(config.id)} ${dim(`⚲ ${filePath}`)}`);
54
+ spinner.start();
55
+
50
56
  const { messages, overrides } = await collectRuntimeMessages(
51
57
  config,
52
58
  config.defaultLocale,
53
59
  exts,
54
60
  customReaders,
55
61
  );
62
+
56
63
  printOverrides(overrides);
57
64
  const schemas = inferSchemas(messages[config.defaultLocale]);
58
65
  buildInputs.push({ id, locales, schemas });
@@ -64,11 +71,12 @@ export async function generate({
64
71
  const types = buildTypes(buildInputs);
65
72
  const schema = buildSchemas(buildInputs);
66
73
 
67
- // Write generated files
68
- const { outDir } = await writeGeneratedFiles({ types, schema });
74
+ // Write files
75
+ await writeTypes(types);
76
+ await writeSchema(schema);
69
77
 
70
78
  spinner.stop();
71
- printSummary(outDir, performance.now() - start);
79
+ printSummary(path.resolve(DEFAULT_OUT_DIR), performance.now() - start);
72
80
  } catch (error) {
73
81
  spinner.stop();
74
82
  console.error(error instanceof Error ? error.message : String(error));
@@ -1,8 +1,8 @@
1
- import pc from "picocolors";
1
+ import { cyan, dim, print } from "../print";
2
2
  import { spinner } from "../spinner";
3
3
 
4
- export function printConfigs(id: string, filePath: string) {
4
+ export function printConfigs(configId: string, filePath: string) {
5
5
  spinner.stop();
6
- console.log(pc.dim("- Config ") + pc.cyan(id) + pc.dim(` ${filePath}`));
6
+ print(`${dim("Config:")} ${cyan(configId)} ⚲ ${filePath}`);
7
7
  spinner.start();
8
8
  }
@@ -1,44 +1,62 @@
1
1
  import type { MergeOverrides } from "../../core/collect-messages/types";
2
- import pc from "picocolors";
2
+ import { dim, gray, print, br } from "../print";
3
3
  import { spinner } from "../spinner";
4
4
 
5
5
  export function printOverrides(overrides: MergeOverrides[]) {
6
- const printable = overrides.filter(
7
- (o) =>
8
- o.kind === "override" &&
9
- (typeof o.prev === "string" || typeof o.next === "string"),
10
- );
11
- if (printable.length === 0) return;
6
+ // Group overrides by layer
7
+ const grouped = groupOverrides(overrides);
8
+
9
+ const hasAny =
10
+ grouped.client_over_server.length > 0 ||
11
+ grouped.runtime_over_static.length > 0;
12
+ if (!hasAny) return;
12
13
 
13
14
  spinner.stop();
14
15
 
15
- console.log(pc.dim(" ↳ Overrides"));
16
+ // Print title
17
+ print(dim("Overrides:"), 1);
16
18
 
17
- // Print deduplicated override entries
18
- for (const { kind, layer, path, prev, next } of overrides) {
19
- if (kind === "add") continue;
19
+ // Print layer group
20
+ printLayerGroup("client > server", grouped.client_over_server);
21
+ printLayerGroup("runtime > static", grouped.runtime_over_static);
22
+
23
+ br();
24
+ spinner.start();
25
+ }
20
26
 
21
- console.log(
22
- pc.dim(" - ") + pc.dim(`(${formatLayer(layer)}) `) + pc.gray(path),
23
- formatDiff(prev, next),
24
- );
27
+ function groupOverrides(overrides: MergeOverrides[]) {
28
+ const groups: Record<MergeOverrides["layer"], MergeOverrides[]> = {
29
+ client_over_server: [],
30
+ runtime_over_static: [],
31
+ };
32
+ for (const o of overrides) {
33
+ if (o.kind !== "override") continue;
34
+ if (typeof o.prev !== "string" && typeof o.next !== "string") continue;
35
+ groups[o.layer].push(o);
25
36
  }
37
+ return groups;
38
+ }
26
39
 
27
- console.log();
28
- spinner.start();
40
+ function printLayerGroup(label: string, items: MergeOverrides[]) {
41
+ if (items.length === 0) return;
42
+ print(dim(label), 2);
43
+ for (const { path, prev, next } of items) {
44
+ print(`- ${path} ${formatDiff(prev, next)}`, 3);
45
+ }
29
46
  }
30
47
 
31
- function formatLayer(layer: "client_over_server" | "runtime_over_static") {
32
- if (layer === "client_over_server") return "client > server";
33
- return "runtime > static";
48
+ const MAX_PREVIEW_LENGTH = 16;
49
+ function truncate(value: unknown, max = MAX_PREVIEW_LENGTH): string {
50
+ if (typeof value !== "string") return "";
51
+ if (value.length <= max) return value;
52
+ return value.slice(0, max) + "…";
34
53
  }
35
54
 
36
- function formatDiff(prev: unknown, next: unknown): string | undefined {
37
- if (typeof prev !== "string" && typeof next !== "string") return;
55
+ function formatDiff(prev: unknown, next: unknown) {
38
56
  return (
39
- pc.dim("| Prev: ") +
40
- pc.gray(typeof prev === "string" ? prev : "") +
41
- pc.dim(", Next: ") +
42
- pc.gray(typeof next === "string" ? next : "")
57
+ dim("| Prev: ") +
58
+ gray(truncate(prev)) +
59
+ dim(" Next: ") +
60
+ gray(truncate(next))
43
61
  );
44
62
  }
@@ -1,18 +1,26 @@
1
- import pc from "picocolors";
1
+ import { bold, br, dim, gray, green, italic, print } from "../print";
2
2
 
3
- const label = (text: string) => pc.dim(text.padEnd(18));
3
+ const label = (text: string) => dim(text.padEnd(18));
4
4
 
5
5
  export function printSummary(outDir: string, ms: number) {
6
- console.log();
7
- console.log(pc.green(pc.bold("✔ intor generate completed")));
8
- console.log();
9
- console.log(label("Output directory: ") + pc.gray(outDir));
10
- console.log(label("Time elapsed: ") + pc.gray(`${(ms / 1000).toFixed(2)}s`));
11
- console.log();
12
- console.log(
13
- pc.dim("💡 Remember to include ") +
14
- pc.gray(".intor/**/*.d.ts") +
15
- pc.dim(" in your tsconfig.json "),
6
+ br();
7
+
8
+ print(green(bold("✔ intor generate completed")));
9
+
10
+ br();
11
+
12
+ print(label("Output directory: ") + gray(outDir));
13
+ print(label("Time elapsed: ") + gray(`${(ms / 1000).toFixed(2)}s`));
14
+
15
+ br();
16
+
17
+ print(
18
+ italic(
19
+ dim("💡 Remember to include ") +
20
+ gray(".intor/**/*.d.ts") +
21
+ dim(" in your tsconfig.json "),
22
+ ),
16
23
  );
17
- console.log();
24
+
25
+ br();
18
26
  }
@@ -1,2 +1,3 @@
1
1
  export { generate } from "./generate";
2
2
  export { check } from "./check";
3
+ export { validate } from "./validate";
@@ -0,0 +1,43 @@
1
+ import pc from "picocolors";
2
+
3
+ export const dim = pc.dim;
4
+ export const cyan = pc.cyan;
5
+ export const green = pc.green;
6
+ export const bold = pc.bold;
7
+ export const italic = pc.italic;
8
+ export const gray = pc.gray;
9
+
10
+ const INDENT = " ";
11
+
12
+ export function print(text = "", indentLevel = 0) {
13
+ if (!text) return;
14
+ console.log(INDENT.repeat(indentLevel) + text);
15
+ }
16
+
17
+ export function br(count = 1) {
18
+ for (let i = 0; i < count; i++) console.log();
19
+ }
20
+
21
+ export function printTitle(title: string) {
22
+ console.log();
23
+ console.log(pc.bgBlack(` • ${title} `));
24
+ console.log();
25
+ }
26
+
27
+ export function printList(
28
+ title: string | null,
29
+ items: readonly string[],
30
+ indentLevel = 0,
31
+ ) {
32
+ if (items.length === 0) return;
33
+ if (title) print(title, indentLevel);
34
+ for (const value of items) {
35
+ print(`- ${value}`, indentLevel + 1);
36
+ }
37
+ }
38
+
39
+ export function printMissingSchema(configId: string) {
40
+ print(`${dim("Config:")} ${cyan(configId)}`);
41
+ print(dim("✖ Missing schema: run `intor generate` and retry"), 1);
42
+ br();
43
+ }
@@ -0,0 +1 @@
1
+ export { validate } from "./validate";
@@ -0,0 +1 @@
1
+ export { validateMessagesSchema } from "./validate-messages-schema";
@@ -0,0 +1,36 @@
1
+ import type { InferNode } from "../../../core";
2
+ import type { ValidationResult } from "../validate-locale-messages";
3
+ import type { MessageObject } from "intor";
4
+
5
+ export function validateMessagesSchema(
6
+ schema: InferNode,
7
+ messages: MessageObject,
8
+ path: string,
9
+ result: ValidationResult,
10
+ ) {
11
+ // Only object schemas define required message keys
12
+ if (schema.kind !== "object") return;
13
+
14
+ // Traverse schema-defined keys and validate message presence
15
+ for (const key of Object.keys(schema.properties)) {
16
+ const nextPath = path ? `${path}.${key}` : key;
17
+ const expected = schema.properties[key];
18
+ const value = messages[key];
19
+
20
+ // Schema requires this key, but message does not provide it
21
+ if (value === undefined) {
22
+ result.missingKeys.push(nextPath);
23
+ continue;
24
+ }
25
+
26
+ // Recurse into nested message objects
27
+ if (typeof value === "object" && value !== null) {
28
+ validateMessagesSchema(
29
+ expected,
30
+ value as MessageObject,
31
+ nextPath,
32
+ result,
33
+ );
34
+ }
35
+ }
36
+ }