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
@@ -1,12 +1,12 @@
1
+ import type { MissingRequirements } from "./collect-missing-requirements";
1
2
  import type { InferNode } from "../../../core";
2
- import type { ValidationResult } from "../validate-locale-messages";
3
3
  import type { MessageObject } from "intor";
4
4
 
5
- export function validateMessagesSchema(
5
+ export function collectMissingMessages(
6
6
  schema: InferNode,
7
7
  messages: MessageObject,
8
8
  path: string,
9
- result: ValidationResult,
9
+ result: MissingRequirements,
10
10
  ) {
11
11
  // Only object schemas define required message keys
12
12
  if (schema.kind !== "object") return;
@@ -19,13 +19,13 @@ export function validateMessagesSchema(
19
19
 
20
20
  // Schema requires this key, but message does not provide it
21
21
  if (value === undefined) {
22
- result.missingKeys.push(nextPath);
22
+ result.missingMessages.push(nextPath);
23
23
  continue;
24
24
  }
25
25
 
26
26
  // Recurse into nested message objects
27
27
  if (typeof value === "object" && value !== null) {
28
- validateMessagesSchema(
28
+ collectMissingMessages(
29
29
  expected,
30
30
  value as MessageObject,
31
31
  nextPath,
@@ -1,13 +1,13 @@
1
+ import type { MissingRequirements } from "./collect-missing-requirements";
1
2
  import type { InferNode } from "../../../core";
2
- import type { ValidationResult } from "../validate-locale-messages";
3
3
  import type { MessageObject } from "intor";
4
4
  import { extractInterpolationNames } from "../../../core";
5
5
 
6
- export function validateReplacementsSchema(
6
+ export function collectMissingReplacements(
7
7
  schema: InferNode,
8
8
  messages: MessageObject,
9
9
  path: string,
10
- result: ValidationResult,
10
+ result: MissingRequirements,
11
11
  ) {
12
12
  // Only object schemas define required message keys
13
13
  if (schema.kind !== "object") return;
@@ -40,7 +40,7 @@ export function validateReplacementsSchema(
40
40
 
41
41
  // Recurse into nested message objects
42
42
  if (typeof value === "object" && value !== null) {
43
- validateReplacementsSchema(
43
+ collectMissingReplacements(
44
44
  expected,
45
45
  value as MessageObject,
46
46
  nextPath,
@@ -0,0 +1,44 @@
1
+ import type { InferredShapes } from "../../../core";
2
+ import type { MessageObject } from "intor";
3
+ import { collectMissingMessages } from "./collect-missing-messages";
4
+ import { collectMissingReplacements } from "./collect-missing-replacements";
5
+ import { collectMissingRich } from "./collect-missing-rich";
6
+
7
+ export interface MissingRequirements {
8
+ missingMessages: string[];
9
+ missingReplacements: Array<{ key: string; name: string }>;
10
+ missingRich: Array<{ key: string; tag: string }>;
11
+ }
12
+
13
+ /**
14
+ * Collect missing translation requirements by comparing
15
+ * inferred semantic schemas with locale messages.
16
+ */
17
+ export function collectMissingRequirements(
18
+ schemas: InferredShapes,
19
+ localeMessages: MessageObject,
20
+ ): MissingRequirements {
21
+ const missingRequirements: MissingRequirements = {
22
+ missingMessages: [],
23
+ missingReplacements: [],
24
+ missingRich: [],
25
+ };
26
+
27
+ collectMissingMessages(
28
+ schemas.messages,
29
+ localeMessages,
30
+ "",
31
+ missingRequirements,
32
+ );
33
+
34
+ collectMissingReplacements(
35
+ schemas.replacements,
36
+ localeMessages,
37
+ "",
38
+ missingRequirements,
39
+ );
40
+
41
+ collectMissingRich(schemas.rich, localeMessages, "", missingRequirements);
42
+
43
+ return missingRequirements;
44
+ }
@@ -1,12 +1,12 @@
1
+ import type { MissingRequirements } from "./collect-missing-requirements";
1
2
  import type { InferNode } from "../../../core";
2
- import type { ValidationResult } from "../validate-locale-messages";
3
3
  import { tokenize, type MessageObject, type Token } from "intor";
4
4
 
5
- export function validateRichSchema(
5
+ export function collectMissingRich(
6
6
  schema: InferNode,
7
7
  messages: MessageObject,
8
8
  path: string,
9
- result: ValidationResult,
9
+ result: MissingRequirements,
10
10
  ) {
11
11
  // Only object schemas define expected rich tags
12
12
  if (schema.kind !== "object") return;
@@ -34,7 +34,7 @@ export function validateRichSchema(
34
34
  // Report any schema-required rich tags missing in the message
35
35
  for (const tag of Object.keys(expected.properties)) {
36
36
  if (actualTags.has(tag)) continue;
37
- result.missingRichTags.push({ key: nextPath, tag });
37
+ result.missingRich.push({ key: nextPath, tag });
38
38
  }
39
39
 
40
40
  continue;
@@ -42,7 +42,7 @@ export function validateRichSchema(
42
42
 
43
43
  // Recurse into nested message objects
44
44
  if (typeof value === "object" && value !== null) {
45
- validateRichSchema(expected, value as MessageObject, nextPath, result);
45
+ collectMissingRich(expected, value as MessageObject, nextPath, result);
46
46
  }
47
47
  }
48
48
  }
@@ -0,0 +1,47 @@
1
+ import type { MissingRequirementsByLocale } from "./types";
2
+ import { createLogger } from "../../logger";
3
+ import { cyan, br, yellow } from "../../render";
4
+ import { renderLocaleBlocks } from "./render-locale-blocks";
5
+
6
+ // { [locale: string]: MissingRequirements; } → { string; MissingRequirements; }[]
7
+ function normalizeMissingByLocale(
8
+ missingByLocale: MissingRequirementsByLocale,
9
+ ) {
10
+ return Object.entries(missingByLocale)
11
+ .map(([locale, missing]) => ({ locale, missing }))
12
+ .filter(
13
+ ({ missing }) =>
14
+ missing.missingMessages.length > 0 ||
15
+ missing.missingReplacements.length > 0 ||
16
+ missing.missingRich.length > 0,
17
+ );
18
+ }
19
+
20
+ export function renderConfigSummary(
21
+ configId: string,
22
+ missingByLocale: MissingRequirementsByLocale,
23
+ enabled = true,
24
+ ) {
25
+ if (!enabled) return;
26
+ const logger = createLogger();
27
+ br();
28
+
29
+ const entries = normalizeMissingByLocale(missingByLocale);
30
+
31
+ if (entries.length === 0) {
32
+ logger.ok(`${cyan(configId)}: no problems found`);
33
+ return;
34
+ }
35
+
36
+ // Log header
37
+ logger.header(
38
+ `${cyan(configId)}: ${yellow(entries.length)} problem locale(s)`,
39
+ { lineBreakAfter: 1 },
40
+ );
41
+
42
+ // Render locale blocks
43
+ renderLocaleBlocks(entries);
44
+
45
+ // Log footer
46
+ logger.footer("", { lineBreakBefore: 1 });
47
+ }
@@ -0,0 +1,56 @@
1
+ import type { MissingRequirements } from "./missing/collect-missing-requirements";
2
+ import { createLogger } from "../../logger";
3
+ import { dim, italic, gray } from "../../render";
4
+
5
+ export function renderLocaleBlocks(
6
+ entries: { locale: string; missing: MissingRequirements }[],
7
+ ) {
8
+ const logger = createLogger();
9
+ const prefix = dim("│ ");
10
+
11
+ for (let i = 0; i < entries.length; i++) {
12
+ const { locale, missing } = entries[i];
13
+ const isLastLocale = i === entries.length - 1;
14
+
15
+ const { missingMessages, missingReplacements, missingRich } = missing;
16
+
17
+ logger.header(italic(locale), { prefix });
18
+ logger.log("", { prefix });
19
+
20
+ let hasPrintedSection = false;
21
+
22
+ // messages
23
+ if (missingMessages.length > 0) {
24
+ logger.log(gray("Missing messages:"), { prefix });
25
+ for (const message of missingMessages) {
26
+ logger.log(` - ${message}`, { prefix });
27
+ }
28
+ hasPrintedSection = true;
29
+ }
30
+
31
+ // replacements
32
+ if (missingReplacements.length > 0) {
33
+ if (hasPrintedSection) logger.log("", { prefix });
34
+ logger.log(gray("Missing replacements:"), { prefix });
35
+ for (const { key, name } of missingReplacements) {
36
+ logger.log(` - ${key}: ${name}`, { prefix });
37
+ }
38
+ hasPrintedSection = true;
39
+ }
40
+
41
+ // rich
42
+ if (missingRich.length > 0) {
43
+ if (hasPrintedSection) logger.log("", { prefix });
44
+ logger.log(gray("Missing rich tags:"), { prefix });
45
+ for (const { key, tag } of missingRich) {
46
+ logger.log(` - ${key}: ${tag}`, { prefix });
47
+ }
48
+ }
49
+
50
+ logger.log("", { prefix });
51
+ logger.footer("", { prefix });
52
+
53
+ // locale ↔ locale spacing
54
+ if (!isLastLocale) logger.log();
55
+ }
56
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReaderOptions } from "../../core";
2
+ import type { MissingRequirements } from "./missing/collect-missing-requirements";
3
+
4
+ export type MissingRequirementsByLocale = {
5
+ [locale: string]: MissingRequirements;
6
+ };
7
+
8
+ export type MissingReport = { [configId: string]: MissingRequirementsByLocale };
9
+
10
+ export interface ValidateOptions extends ReaderOptions {
11
+ format?: "human" | "json";
12
+ output?: string;
13
+ debug?: boolean;
14
+ }
@@ -1,83 +1,78 @@
1
1
  /* eslint-disable unicorn/no-process-exit */
2
- import { discoverConfigs, readSchema, type ExtraExt } from "../../core";
2
+ import type {
3
+ MissingReport,
4
+ MissingRequirementsByLocale,
5
+ ValidateOptions,
6
+ } from "./types";
7
+ import { features } from "../../constants";
8
+ import { discoverConfigs, readSchema } from "../../core";
3
9
  import { collectOtherLocaleMessages } from "../../core";
4
- import { printMissingSchema, printTitle } from "../print";
5
- import { spinner } from "../spinner";
6
- import { printSummary } from "./print-summary";
7
- import {
8
- validateLocaleMessages,
9
- type ValidationResult,
10
- } from "./validate-locale-messages";
11
-
12
- export interface ValidateOptions {
13
- exts?: Array<ExtraExt>;
14
- customReaders?: Record<string, string>;
15
- debug?: boolean;
16
- }
10
+ import { renderMissingConfigSchema, renderTitle } from "../../render";
11
+ import { spinner } from "../shared/spinner";
12
+ import { writeJsonReport } from "../shared/write-json-report";
13
+ import { collectMissingRequirements } from "./missing/collect-missing-requirements";
14
+ import { renderConfigSummary } from "./render-config-summary";
17
15
 
18
16
  export async function validate({
19
17
  exts = [],
20
18
  customReaders,
19
+ format = "human",
20
+ output,
21
21
  debug,
22
22
  }: ValidateOptions) {
23
- printTitle("Validating intor translations");
24
- spinner.start();
23
+ const isHuman = format === "human";
24
+ renderTitle(features.validate.title, isHuman);
25
25
 
26
26
  // -----------------------------------------------------------------------
27
27
  // Discover configs from the current workspace
28
28
  // -----------------------------------------------------------------------
29
29
  const configEntries = await discoverConfigs(debug);
30
- if (configEntries.length === 0) {
31
- spinner.stop();
32
- throw new Error("No Intor config found.");
33
- }
30
+ if (configEntries.length === 0) throw new Error("No Intor config found.");
34
31
 
35
32
  try {
36
- // -----------------------------------------------------------------------
33
+ // ---------------------------------------------------------------------
37
34
  // Read generated schema
38
- // -----------------------------------------------------------------------
35
+ // ---------------------------------------------------------------------
39
36
  const schema = await readSchema();
40
-
41
- const resultsByConfig: Record<
42
- string,
43
- Record<string, ValidationResult>
44
- > = {};
37
+ const report: MissingReport = {};
45
38
 
46
39
  // Per-config processing
47
40
  for (const { config } of configEntries) {
48
- const schemaConfig = schema.configs.find((c) => c.id === config.id);
41
+ const schemaConfig = schema.entries.find((c) => c.id === config.id);
49
42
  if (!schemaConfig) {
50
- spinner.stop();
51
- printMissingSchema(config.id);
52
- spinner.start();
43
+ renderMissingConfigSchema(config.id, isHuman);
53
44
  continue;
54
45
  }
46
+ const { shapes } = schemaConfig;
55
47
 
48
+ // -------------------------------------------------------------------
56
49
  // Load all non-default locale messages
57
- const localeMessages = await collectOtherLocaleMessages(
58
- config,
50
+ // -------------------------------------------------------------------
51
+ if (isHuman) spinner.start();
52
+ const localeMessages = await collectOtherLocaleMessages(config, {
59
53
  exts,
60
54
  customReaders,
61
- );
55
+ });
56
+ if (isHuman) spinner.stop();
62
57
 
63
- const results: Record<string, ValidationResult> = {};
58
+ // -------------------------------------------------------------------
59
+ // Collect missing requirements per locale
60
+ // -------------------------------------------------------------------
61
+ const missingByLocale: MissingRequirementsByLocale = {};
64
62
  for (const locale of config.supportedLocales) {
65
63
  if (locale === config.defaultLocale) continue;
66
64
  const messages = localeMessages[locale];
67
65
  if (!messages) continue;
68
- results[locale] = validateLocaleMessages(
69
- schemaConfig.schemas,
70
- messages,
71
- );
66
+ missingByLocale[locale] = collectMissingRequirements(shapes, messages);
72
67
  }
73
68
 
74
- resultsByConfig[config.id] = results;
69
+ report[config.id] = missingByLocale;
70
+ renderConfigSummary(config.id, missingByLocale, isHuman);
75
71
  }
76
72
 
77
- spinner.stop();
78
- printSummary(resultsByConfig);
73
+ if (format === "json") await writeJsonReport(report, output);
79
74
  } catch (error) {
80
- spinner.stop();
75
+ if (isHuman) spinner.stop();
81
76
  console.error(error instanceof Error ? error.message : String(error));
82
77
  process.exit(1);
83
78
  }
package/src/logger.ts ADDED
@@ -0,0 +1,95 @@
1
+ import { dim, green, red, yellow } from "./render";
2
+
3
+ type Logger = {
4
+ header: (message?: string, options?: LogOptions) => void;
5
+ log: (message?: string, options?: LogOptions) => void;
6
+ footer: (message?: string, options?: LogOptions) => void;
7
+ process: (tag: ProcessTag, message: string) => void;
8
+ ok: (message?: string) => void;
9
+ error: (message?: string) => void;
10
+ };
11
+
12
+ const noop = () => {};
13
+ const noopLogger: Logger = {
14
+ header: noop,
15
+ log: noop,
16
+ footer: noop,
17
+ process: noop,
18
+ ok: noop,
19
+ error: noop,
20
+ };
21
+
22
+ type LogOptions = {
23
+ prefix?: string;
24
+ kind?: "process";
25
+ lineBreakBefore?: number;
26
+ lineBreakAfter?: number;
27
+ };
28
+
29
+ const SPACER = dim("│ ");
30
+ const log = console.log;
31
+
32
+ export function createLogger(enabled = true): Logger {
33
+ if (!enabled) return noopLogger;
34
+
35
+ return {
36
+ header: (message = "", options: LogOptions = {}) => {
37
+ const prefix = options.prefix ?? "";
38
+ const symbol = options.kind === "process" ? dim("○ ") : "";
39
+ const before = options.lineBreakBefore ?? 0;
40
+ const after = options.lineBreakAfter ?? 0;
41
+
42
+ for (let i = 0; i < before; i++) log(SPACER);
43
+ log(prefix + dim("┌─ ") + symbol + message);
44
+ for (let i = 0; i < after; i++) log(SPACER);
45
+ },
46
+
47
+ log: (message = "", options: LogOptions = {}) => {
48
+ const prefix = options.prefix ?? "";
49
+ log(prefix + SPACER + message);
50
+ },
51
+
52
+ footer: (message = "", options: LogOptions = {}) => {
53
+ const prefix = options.prefix ?? "";
54
+ const symbol = options.kind === "process" ? dim("● ") : "";
55
+ const before = options.lineBreakBefore ?? 0;
56
+ const after = options.lineBreakAfter ?? 0;
57
+
58
+ for (let i = 0; i < before; i++) log(SPACER);
59
+ log(prefix + dim("└─ ") + symbol + message);
60
+ for (let i = 0; i < after; i++) log(SPACER);
61
+ },
62
+
63
+ process: (tag: ProcessTag, message: string) => {
64
+ log(...formatProcessLog(tag, message));
65
+ },
66
+
67
+ ok: (message = "") => {
68
+ log(dim("╶─ ") + green("✔ ") + message);
69
+ },
70
+
71
+ error: (message = "") => {
72
+ log(dim("╶─ ") + red("✖ ") + message);
73
+ },
74
+ };
75
+ }
76
+
77
+ // Format process log
78
+ type ProcessTag = "ok" | "warn" | "skip" | "load" | string;
79
+ type TagStyle = {
80
+ color?: (s: string) => string;
81
+ dimMessage?: boolean;
82
+ };
83
+ const TAG_STYLES: Record<string, TagStyle> = {
84
+ ok: { color: green },
85
+ warn: { color: yellow },
86
+ skip: { color: dim, dimMessage: true },
87
+ load: {},
88
+ };
89
+ const formatProcessLog = (tag: ProcessTag, message: string) => {
90
+ const style = TAG_STYLES[tag] ?? {};
91
+ const paddedTag = tag.padEnd(4, " ");
92
+ const finalTag = style.color ? style.color(paddedTag) : paddedTag;
93
+ const finalMessage = style.dimMessage ? dim(message) : message;
94
+ return [SPACER + dim("│ ") + finalTag + dim(" │ ") + finalMessage];
95
+ };
package/src/render.ts ADDED
@@ -0,0 +1,57 @@
1
+ import type { ConfigEntry } from "./core";
2
+ import pc from "picocolors";
3
+ import { toRelativePath } from "./features/shared/to-relative-path";
4
+ import { createLogger } from "./logger";
5
+
6
+ // Formatting utilities
7
+ export const dim = pc.dim;
8
+ export const cyan = pc.cyan;
9
+ export const green = pc.green;
10
+ export const bold = pc.bold;
11
+ export const italic = pc.italic;
12
+ export const gray = pc.gray;
13
+ export const yellow = pc.yellow;
14
+ export const red = pc.red;
15
+ export const bgBlack = pc.bgBlack;
16
+
17
+ // Layout helpers
18
+ export function br(count = 1, enabled = true) {
19
+ if (!enabled) return;
20
+ for (let i = 0; i < count; i++) console.log();
21
+ }
22
+
23
+ // Render feature title
24
+ export function renderTitle(title = "", enabled = true) {
25
+ if (!enabled) return;
26
+ console.log(" " + bgBlack(` ${title} `));
27
+ }
28
+
29
+ // Render discovered Intor configs
30
+ export function renderConfigs(configEntries: ConfigEntry[], enabled = true) {
31
+ if (!enabled) return;
32
+ const logger = createLogger();
33
+
34
+ // Log header
35
+ logger.header(`Discovered ${yellow(configEntries.length)} Intor config(s):`, {
36
+ lineBreakAfter: 1,
37
+ });
38
+
39
+ for (const { filePath, config } of configEntries) {
40
+ logger.log(`${cyan(config.id)} ${dim(`⚲ ${toRelativePath(filePath)}`)}`);
41
+ }
42
+
43
+ // Log footer
44
+ logger.footer("", { lineBreakBefore: 1 });
45
+ }
46
+
47
+ // Render schema-not-found message for config
48
+ export function renderMissingConfigSchema(configId: string, enabled = true) {
49
+ if (!enabled) return;
50
+ const logger = createLogger();
51
+
52
+ br();
53
+ logger.error(
54
+ cyan(configId) +
55
+ `: schema not found, run ${italic("intor generate")} and retry`,
56
+ );
57
+ }
@@ -1,13 +0,0 @@
1
- import type { BuildInput, Schema } from "../types";
2
-
3
- export function buildSchemas(inputs: BuildInput[]): Schema {
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
- }
@@ -1 +0,0 @@
1
- export { buildSchemas } from "./build-schemas";
@@ -1,3 +0,0 @@
1
- export { buildTypes } from "./build-types";
2
- export { buildSchemas } from "./build-schemas";
3
- export type { BuildInput, Schema } from "./types";
@@ -1,20 +0,0 @@
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 Schema {
11
- version: number;
12
- generatedAt: string;
13
- configs: SchemaConfig[];
14
- }
15
-
16
- export interface SchemaConfig {
17
- id: string;
18
- locales: readonly string[];
19
- schemas: InferredSchemas;
20
- }
@@ -1,6 +0,0 @@
1
- export { writeTypes } from "./write-types";
2
-
3
- export { writeSchema } from "./write-schema";
4
- export { readSchema } from "./read-schema";
5
-
6
- export { writeMessagesSnapshot } from "./write-messages-snapshot";
@@ -1,27 +0,0 @@
1
- /* eslint-disable unicorn/no-array-sort */
2
- import type { MessageObject } from "intor";
3
- import path from "node:path";
4
- import { DEFAULT_OUT_DIR, DEFAULT_MESSAGES_SNAPSHOT_DIR } from "../constants";
5
- import { ensureAndWrite } from "./ensure-and-write";
6
-
7
- export async function writeMessagesSnapshot(
8
- localeMessages: Record<string, MessageObject>,
9
- ): Promise<string[]> {
10
- const filePaths: string[] = [];
11
-
12
- for (const locale of Object.keys(localeMessages).sort()) {
13
- const messages = localeMessages[locale];
14
-
15
- const filePath = path.join(
16
- DEFAULT_OUT_DIR,
17
- DEFAULT_MESSAGES_SNAPSHOT_DIR,
18
- `${locale}.json`,
19
- );
20
-
21
- await ensureAndWrite(filePath, JSON.stringify(messages, null, 2));
22
-
23
- filePaths.push(filePath);
24
- }
25
-
26
- return filePaths;
27
- }
@@ -1,9 +0,0 @@
1
- import type { Schema } from "../../build";
2
- import path from "node:path";
3
- import { DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME } from "../constants";
4
- import { ensureAndWrite } from "./ensure-and-write";
5
-
6
- export async function writeSchema(schema: Schema): Promise<string> {
7
- const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_SCHEMA_FILE_NAME);
8
- return await ensureAndWrite(filePath, JSON.stringify(schema, null, 2));
9
- }
@@ -1,8 +0,0 @@
1
- import path from "node:path";
2
- import { DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME } from "../constants";
3
- import { ensureAndWrite } from "./ensure-and-write";
4
-
5
- export async function writeTypes(types: string): Promise<string> {
6
- const filePath = path.join(DEFAULT_OUT_DIR, DEFAULT_TYPES_FILE_NAME);
7
- return await ensureAndWrite(filePath, types);
8
- }
@@ -1,4 +0,0 @@
1
- export { inferSchemas } from "./infer-schemas";
2
- export type { InferNode, InferredSchemas } from "./types";
3
-
4
- export { extractInterpolationNames } from "./replacements/extract-interpolation-names";
@@ -1,20 +0,0 @@
1
- import type { InferredSchemas } from "./types";
2
- import type { MessageObject } from "intor";
3
- import { inferMessagesSchema } from "./messages";
4
- import { inferReplacementsSchema } from "./replacements";
5
- import { inferRichSchema } from "./rich";
6
- import { stripInternalKeys } from "./utils/strip-internal-keys";
7
-
8
- /**
9
- * Infer all semantic schemas from messages.
10
- */
11
- export function inferSchemas(messages: MessageObject): InferredSchemas {
12
- const schemas = {
13
- messagesSchema: inferMessagesSchema(messages),
14
- replacementsSchema: inferReplacementsSchema(messages),
15
- richSchema: inferRichSchema(messages),
16
- };
17
-
18
- stripInternalKeys(schemas);
19
- return schemas;
20
- }
@@ -1 +0,0 @@
1
- export { inferMessagesSchema } from "./infer-messages-schema";
@@ -1 +0,0 @@
1
- export { inferReplacementsSchema } from "./infer-replacements-schema";
@@ -1 +0,0 @@
1
- export { inferRichSchema } from "./infer-rich-schema";