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.
- package/package.json +2 -3
- package/src/cli/commands/check.ts +23 -17
- package/src/cli/commands/discover.ts +32 -0
- package/src/cli/commands/generate.ts +35 -40
- package/src/cli/commands/index.ts +4 -0
- package/src/cli/commands/options/index.ts +1 -0
- package/src/cli/commands/options/options.ts +55 -0
- package/src/cli/commands/utils/normalize-message-files.ts +49 -0
- package/src/cli/commands/utils/normalize-reader-options.ts +15 -28
- package/src/cli/commands/validate.ts +26 -30
- package/src/cli/index.ts +38 -23
- package/src/cli/menu/index.ts +1 -0
- package/src/cli/menu/prompts/prompt-check.ts +74 -0
- package/src/cli/menu/prompts/prompt-discover.ts +25 -0
- package/src/cli/menu/prompts/prompt-generate.ts +106 -0
- package/src/cli/menu/prompts/prompt-validate.ts +49 -0
- package/src/cli/menu/prompts/shared/prompt-reader-options.ts +63 -0
- package/src/cli/menu/prompts/shared/shared.ts +76 -0
- package/src/cli/menu/run.ts +72 -0
- package/src/cli/version.ts +3 -0
- package/src/constants.ts +6 -0
- package/src/core/artifacts/index.ts +5 -0
- package/src/core/artifacts/schema/build-schema.ts +13 -0
- package/src/core/artifacts/schema/index.ts +3 -0
- package/src/core/{generated → artifacts/schema}/read-schema.ts +4 -4
- package/src/core/artifacts/schema/write-schema.ts +14 -0
- package/src/{build/build-types → core/artifacts/types/build}/build-types.ts +9 -10
- package/src/{build/build-types → core/artifacts/types/build}/utils/normalize-rich-infer-node.ts +1 -1
- package/src/{build/build-types → core/artifacts/types/build}/utils/render-infer-node.ts +1 -1
- package/src/core/artifacts/types/index.ts +2 -0
- package/src/core/artifacts/types/write-types.ts +8 -0
- package/src/core/artifacts/types.ts +20 -0
- package/src/core/collect-messages/collect-other-locale-messages.ts +5 -7
- package/src/core/collect-messages/collect-runtime-messages.ts +8 -6
- package/src/core/collect-messages/index.ts +1 -0
- package/src/core/collect-messages/readers.ts +1 -0
- package/src/core/collect-messages/types.ts +7 -1
- package/src/core/constants/index.ts +2 -0
- package/src/core/discover-configs/discover-configs.ts +47 -26
- package/src/core/extract-usages/extract-usages.ts +33 -24
- package/src/core/index.ts +12 -7
- package/src/core/infer-shape/index.ts +4 -0
- package/src/core/{infer-schema/messages/infer-messages-schema.ts → infer-shape/infer-messages-shape.ts} +5 -10
- package/src/core/{infer-schema/replacements/infer-replacements-schema.ts → infer-shape/infer-replacements-shape.ts} +6 -11
- package/src/core/{infer-schema/rich/infer-rich-schema.ts → infer-shape/infer-rich-shape.ts} +5 -10
- package/src/core/infer-shape/infer-shapes.ts +21 -0
- package/src/core/{infer-schema → infer-shape}/types.ts +4 -4
- package/src/core/scan/index.ts +2 -0
- package/src/core/{extract-usages/load-source-files-from-tscofnig.ts → scan/load-source-files.ts} +34 -15
- package/src/core/scan/scan-files.ts +25 -0
- package/src/features/check/build-scoped-usages.ts +35 -0
- package/src/features/check/check.ts +51 -53
- package/src/features/check/diagnostics/collect.ts +6 -2
- package/src/features/check/diagnostics/group.ts +0 -1
- package/src/features/check/index.ts +1 -0
- package/src/features/check/render-config-summary.ts +47 -0
- package/src/features/check/types.ts +12 -0
- package/src/features/discover/discover.ts +22 -0
- package/src/features/discover/index.ts +1 -0
- package/src/features/generate/generate.ts +56 -49
- package/src/features/generate/index.ts +1 -0
- package/src/features/generate/render-overrides.ts +73 -0
- package/src/features/generate/render-summary.ts +28 -0
- package/src/features/generate/types.ts +12 -0
- package/src/features/generate/utils/resolve-message-source.ts +20 -0
- package/src/features/generate/utils/validate-message-source.ts +53 -0
- package/src/features/index.ts +4 -3
- package/src/features/shared/to-relative-path.ts +10 -0
- package/src/features/shared/write-json-report.ts +19 -0
- package/src/features/validate/index.ts +1 -0
- package/src/features/validate/{messages/validate-messages-schema.ts → missing/collect-missing-messages.ts} +5 -5
- package/src/features/validate/{replacements/validate-replacements-schema.ts → missing/collect-missing-replacements.ts} +4 -4
- package/src/features/validate/missing/collect-missing-requirements.ts +44 -0
- package/src/features/validate/{rich/validate-rich-schema.ts → missing/collect-missing-rich.ts} +5 -5
- package/src/features/validate/render-config-summary.ts +47 -0
- package/src/features/validate/render-locale-blocks.ts +56 -0
- package/src/features/validate/types.ts +14 -0
- package/src/features/validate/validate.ts +38 -43
- package/src/logger.ts +95 -0
- package/src/render.ts +57 -0
- package/src/build/build-schemas/build-schemas.ts +0 -13
- package/src/build/build-schemas/index.ts +0 -1
- package/src/build/index.ts +0 -3
- package/src/build/types.ts +0 -20
- package/src/core/generated/index.ts +0 -6
- package/src/core/generated/write-messages-snapshot.ts +0 -27
- package/src/core/generated/write-schema.ts +0 -9
- package/src/core/generated/write-types.ts +0 -8
- package/src/core/infer-schema/index.ts +0 -4
- package/src/core/infer-schema/infer-schemas.ts +0 -20
- package/src/core/infer-schema/messages/index.ts +0 -1
- package/src/core/infer-schema/replacements/index.ts +0 -1
- package/src/core/infer-schema/rich/index.ts +0 -1
- package/src/core/scan-logger.ts +0 -10
- package/src/features/check/print-summary.ts +0 -28
- package/src/features/generate/print-configs.ts +0 -8
- package/src/features/generate/print-overrides.ts +0 -62
- package/src/features/generate/print-summary.ts +0 -26
- package/src/features/print.ts +0 -43
- package/src/features/validate/messages/index.ts +0 -1
- package/src/features/validate/print-summary.ts +0 -65
- package/src/features/validate/replacements/index.ts +0 -1
- package/src/features/validate/rich/index.ts +0 -1
- package/src/features/validate/validate-locale-messages.ts +0 -38
- /package/src/core/{generated → artifacts}/ensure-and-write.ts +0 -0
- /package/src/{build/build-types → core/artifacts/types/build}/index.ts +0 -0
- /package/src/{build/build-types → core/artifacts/types/build}/output/append-config-block.ts +0 -0
- /package/src/{build/build-types → core/artifacts/types/build}/output/append-footer.ts +0 -0
- /package/src/{build/build-types → core/artifacts/types/build}/output/append-header.ts +0 -0
- /package/src/{build/build-types → core/artifacts/types/build}/output/index.ts +0 -0
- /package/src/{build/build-types → core/artifacts/types/build}/utils/indent.ts +0 -0
- /package/src/core/{infer-schema/replacements → infer-shape/utils}/extract-interpolation-names.ts +0 -0
- /package/src/core/{infer-schema → infer-shape}/utils/infer-object.ts +0 -0
- /package/src/core/{infer-schema → infer-shape}/utils/is-message-object.ts +0 -0
- /package/src/core/{infer-schema → infer-shape}/utils/should-skip-key.ts +0 -0
- /package/src/core/{infer-schema → infer-shape}/utils/strip-internal-keys.ts +0 -0
- /package/src/features/{spinner.ts → shared/spinner.ts} +0 -0
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import fg from "fast-glob";
|
|
4
3
|
import { type IntorResolvedConfig } from "intor";
|
|
5
|
-
import { createLogger } from "
|
|
4
|
+
import { createLogger } from "../../logger";
|
|
5
|
+
import { br, cyan, yellow } from "../../render";
|
|
6
|
+
import { scanFiles } from "../scan";
|
|
6
7
|
import { isIntorResolvedConfig } from "./is-intor-resolved-config";
|
|
7
8
|
import { loadModule } from "./load-module";
|
|
8
9
|
|
|
9
|
-
const DEFAULT_PATTERNS = ["**/*.{ts,js}"];
|
|
10
|
-
const DEFAULT_IGNORE = [
|
|
11
|
-
"**/node_modules/**",
|
|
12
|
-
"**/dist/**",
|
|
13
|
-
"**/*.d.ts",
|
|
14
|
-
"**/*.test.*",
|
|
15
|
-
"**/*.test-d.ts",
|
|
16
|
-
];
|
|
17
|
-
|
|
18
10
|
export interface ConfigEntry {
|
|
19
11
|
filePath: string;
|
|
20
12
|
config: IntorResolvedConfig;
|
|
@@ -22,13 +14,23 @@ export interface ConfigEntry {
|
|
|
22
14
|
|
|
23
15
|
/**
|
|
24
16
|
* Discover and resolve Intor configs from the current workspace.
|
|
17
|
+
*
|
|
18
|
+
* Notes:
|
|
19
|
+
* - Configs must be declared via `defineIntorConfig(...)`
|
|
20
|
+
* - Dynamic or computed configs are intentionally not supported
|
|
25
21
|
*/
|
|
26
|
-
export async function discoverConfigs(debug
|
|
27
|
-
const files = await
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
export async function discoverConfigs(debug = false): Promise<ConfigEntry[]> {
|
|
23
|
+
const files = await scanFiles();
|
|
24
|
+
|
|
25
|
+
if (debug) br();
|
|
26
|
+
const logger = createLogger(debug);
|
|
27
|
+
logger.header(
|
|
28
|
+
`Discover configs - scanning ${yellow(files.length)} candidate files`,
|
|
29
|
+
{ kind: "process", lineBreakAfter: 1 },
|
|
30
|
+
);
|
|
30
31
|
|
|
31
32
|
const configEntries: ConfigEntry[] = [];
|
|
33
|
+
const seenIds = new Set<string>();
|
|
32
34
|
|
|
33
35
|
// Iterate through candidate files
|
|
34
36
|
for (const file of files) {
|
|
@@ -42,7 +44,7 @@ export async function discoverConfigs(debug?: boolean): Promise<ConfigEntry[]> {
|
|
|
42
44
|
try {
|
|
43
45
|
content = await fs.promises.readFile(absPath, "utf8");
|
|
44
46
|
} catch {
|
|
45
|
-
|
|
47
|
+
logger.process("warn", `failed to read ${relPath}`);
|
|
46
48
|
continue;
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -50,10 +52,10 @@ export async function discoverConfigs(debug?: boolean): Promise<ConfigEntry[]> {
|
|
|
50
52
|
// Skip files that clearly do not define an Intor config
|
|
51
53
|
// ----------------------------------------------------------------------
|
|
52
54
|
if (!content.includes("defineIntorConfig(")) {
|
|
53
|
-
|
|
55
|
+
logger.process("skip", `${relPath} (missing defineIntorConfig)`);
|
|
54
56
|
continue;
|
|
55
57
|
}
|
|
56
|
-
|
|
58
|
+
logger.process("load", relPath);
|
|
57
59
|
|
|
58
60
|
// ----------------------------------------------------------------------
|
|
59
61
|
// Dynamic import & export inspection
|
|
@@ -61,28 +63,47 @@ export async function discoverConfigs(debug?: boolean): Promise<ConfigEntry[]> {
|
|
|
61
63
|
try {
|
|
62
64
|
const moduleExports = await loadModule(absPath);
|
|
63
65
|
let matched = false;
|
|
66
|
+
let resolvedCount = 0;
|
|
64
67
|
|
|
68
|
+
// Loop through all exports
|
|
65
69
|
for (const module of Object.values(moduleExports)) {
|
|
66
70
|
const config = module as IntorResolvedConfig;
|
|
67
|
-
if (!isIntorResolvedConfig(
|
|
68
|
-
|
|
71
|
+
if (!isIntorResolvedConfig(module)) continue;
|
|
69
72
|
matched = true;
|
|
70
73
|
|
|
74
|
+
// Ensure config ids are unique across the workspace
|
|
75
|
+
if (seenIds.has(module.id)) {
|
|
76
|
+
logger.process(
|
|
77
|
+
"warn",
|
|
78
|
+
`duplicate config id "${module.id}" (ignored, ${relPath})`,
|
|
79
|
+
);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
seenIds.add(module.id);
|
|
84
|
+
resolvedCount++;
|
|
85
|
+
|
|
71
86
|
configEntries.push({ filePath: absPath, config });
|
|
72
|
-
|
|
87
|
+
logger.process("ok", `resolved config ${cyan(config.id)}`);
|
|
73
88
|
}
|
|
74
89
|
|
|
75
|
-
if (
|
|
76
|
-
|
|
90
|
+
if (matched && resolvedCount === 0) {
|
|
91
|
+
logger.process("warn", `no usable Intor config export (${relPath})`);
|
|
77
92
|
}
|
|
78
93
|
} catch {
|
|
79
|
-
|
|
94
|
+
logger.process("warn", `failed to import module (${relPath})`);
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
if (configEntries.length === 0) {
|
|
84
|
-
|
|
99
|
+
logger.process("warn", "no Intor config discovered");
|
|
85
100
|
}
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
logger.footer(
|
|
103
|
+
`scanned ${yellow(files.length)} files, resolved ${yellow(
|
|
104
|
+
configEntries.length,
|
|
105
|
+
)} Intor config(s)`,
|
|
106
|
+
{ kind: "process", lineBreakBefore: 1 },
|
|
107
|
+
);
|
|
108
|
+
return configEntries.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
88
109
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExtractedUsages } from "./types";
|
|
2
|
-
import
|
|
3
|
-
import { createLogger } from "
|
|
2
|
+
import type { SourceFile } from "ts-morph";
|
|
3
|
+
import { createLogger } from "../../logger";
|
|
4
|
+
import { br, yellow } from "../../render";
|
|
4
5
|
import { extractUsagesFromSourceFile } from "./extract-usages-from-source-file";
|
|
5
|
-
import { loadSourceFilesFromTsconfig } from "./load-source-files-from-tscofnig";
|
|
6
6
|
|
|
7
7
|
/** Check whether a file-level extraction produced any meaningful usage */
|
|
8
8
|
const isEmpty = (u: ExtractedUsages) =>
|
|
@@ -12,16 +12,23 @@ const isEmpty = (u: ExtractedUsages) =>
|
|
|
12
12
|
u.trans.length === 0;
|
|
13
13
|
|
|
14
14
|
export interface ExtractUsagesOptions {
|
|
15
|
-
|
|
15
|
+
sourceFiles?: SourceFile[];
|
|
16
16
|
debug?: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Extract all static translator usages from a TypeScript project.
|
|
21
21
|
*/
|
|
22
|
-
export function extractUsages(
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
export function extractUsages({
|
|
23
|
+
sourceFiles = [],
|
|
24
|
+
debug = false,
|
|
25
|
+
}: ExtractUsagesOptions = {}): ExtractedUsages {
|
|
26
|
+
if (debug) br();
|
|
27
|
+
const logger = createLogger(debug);
|
|
28
|
+
logger.header(
|
|
29
|
+
`Extract usages - processing ${yellow(sourceFiles.length)} source files`,
|
|
30
|
+
{ kind: "process", lineBreakAfter: 1 },
|
|
31
|
+
);
|
|
25
32
|
|
|
26
33
|
const result: ExtractedUsages = {
|
|
27
34
|
preKey: [],
|
|
@@ -35,36 +42,38 @@ export function extractUsages(options?: ExtractUsagesOptions): ExtractedUsages {
|
|
|
35
42
|
let scannedFiles = 0;
|
|
36
43
|
let matchedFiles = 0;
|
|
37
44
|
|
|
38
|
-
const sourceFiles = loadSourceFilesFromTsconfig(tsconfigPath, debug);
|
|
39
|
-
|
|
40
45
|
// Process each source file independently
|
|
41
46
|
for (const sourceFile of sourceFiles) {
|
|
42
47
|
scannedFiles++;
|
|
43
|
-
log("scan", sourceFile.getFilePath());
|
|
44
48
|
|
|
45
49
|
// ---------------------------------------------------------------------------
|
|
46
50
|
// File-level extraction (pure analysis, no side effects)
|
|
47
51
|
// ---------------------------------------------------------------------------
|
|
48
|
-
const
|
|
49
|
-
|
|
52
|
+
const partialUsages = extractUsagesFromSourceFile(sourceFile);
|
|
53
|
+
|
|
54
|
+
if (isEmpty(partialUsages)) {
|
|
55
|
+
logger.process("skip", sourceFile.getFilePath());
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logger.process("ok", sourceFile.getFilePath());
|
|
50
60
|
matchedFiles++;
|
|
51
61
|
|
|
52
62
|
// ---------------------------------------------------------------------------
|
|
53
63
|
// Merge file-level results into the project-level result
|
|
54
64
|
// ---------------------------------------------------------------------------
|
|
55
|
-
result.preKey.push(...
|
|
56
|
-
result.key.push(...
|
|
57
|
-
result.replacement.push(...
|
|
58
|
-
result.rich.push(...
|
|
59
|
-
result.trans.push(...
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Debug summary
|
|
63
|
-
if (debug) {
|
|
64
|
-
console.log(
|
|
65
|
-
pc.dim(` › Scanned ${scannedFiles} files, matched ${matchedFiles} \n`),
|
|
66
|
-
);
|
|
65
|
+
result.preKey.push(...partialUsages.preKey);
|
|
66
|
+
result.key.push(...partialUsages.key);
|
|
67
|
+
result.replacement.push(...partialUsages.replacement);
|
|
68
|
+
result.rich.push(...partialUsages.rich);
|
|
69
|
+
result.trans.push(...partialUsages.trans);
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
logger.footer(
|
|
73
|
+
`scanned ${yellow(scannedFiles)} files, extracted from ${yellow(
|
|
74
|
+
matchedFiles,
|
|
75
|
+
)} file(s)`,
|
|
76
|
+
{ kind: "process", lineBreakBefore: 1 },
|
|
77
|
+
);
|
|
69
78
|
return result;
|
|
70
79
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
// discover-configs
|
|
2
|
-
export { discoverConfigs } from "./discover-configs";
|
|
2
|
+
export { discoverConfigs, type ConfigEntry } from "./discover-configs";
|
|
3
3
|
|
|
4
4
|
// collect-messages
|
|
5
5
|
export {
|
|
6
6
|
collectRuntimeMessages,
|
|
7
7
|
collectOtherLocaleMessages,
|
|
8
|
+
type ReaderOptions,
|
|
9
|
+
type MergeOverrides,
|
|
8
10
|
} from "./collect-messages";
|
|
9
11
|
|
|
10
12
|
// infer-schema
|
|
11
13
|
export {
|
|
12
|
-
|
|
14
|
+
inferShapes,
|
|
13
15
|
type InferNode,
|
|
14
|
-
type
|
|
16
|
+
type InferredShapes,
|
|
15
17
|
extractInterpolationNames,
|
|
16
|
-
} from "./infer-
|
|
18
|
+
} from "./infer-shape";
|
|
17
19
|
|
|
18
20
|
// extract-usages
|
|
19
21
|
export {
|
|
@@ -28,13 +30,16 @@ export {
|
|
|
28
30
|
type RichUsage,
|
|
29
31
|
} from "./extract-usages";
|
|
30
32
|
|
|
31
|
-
//
|
|
33
|
+
// artifacts
|
|
32
34
|
export {
|
|
35
|
+
buildTypes,
|
|
33
36
|
writeTypes,
|
|
37
|
+
buildSchema,
|
|
34
38
|
writeSchema,
|
|
35
39
|
readSchema,
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
type GeneratedSchema,
|
|
41
|
+
type SchemaEntry,
|
|
42
|
+
} from "./artifacts";
|
|
38
43
|
|
|
39
44
|
// constants
|
|
40
45
|
export { DEFAULT_OUT_DIR, EXTRA_EXTS, type ExtraExt } from "./constants";
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import type { InferNode } from "
|
|
1
|
+
import type { InferNode } from "./types";
|
|
2
2
|
import type { MessageObject, MessageValue } from "intor";
|
|
3
|
-
import { inferObject } from "
|
|
4
|
-
import { isMessageObject } from "
|
|
3
|
+
import { inferObject } from "./utils/infer-object";
|
|
4
|
+
import { isMessageObject } from "./utils/is-message-object";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Infer message
|
|
8
|
-
*
|
|
9
|
-
* Traverses message values and derives a semantic schema tree.
|
|
10
|
-
* Rendering to TypeScript type string should be done in a later stage.
|
|
7
|
+
* Infer the semantic shape of message values from a message object.
|
|
11
8
|
*/
|
|
12
|
-
export function
|
|
9
|
+
export function inferMessagesShape(messages: MessageObject): InferNode {
|
|
13
10
|
if (!isMessageObject(messages) || Object.keys(messages).length === 0) {
|
|
14
11
|
return { kind: "none" };
|
|
15
12
|
}
|
|
@@ -17,8 +14,6 @@ export function inferMessagesSchema(messages: MessageObject): InferNode {
|
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
|
-
* Infer a semantic node from a single message value.
|
|
21
|
-
*
|
|
22
17
|
* - Primitive values → primitive node
|
|
23
18
|
* - Arrays → array node (first-element policy)
|
|
24
19
|
* - Objects → object node (recursive, empty pruned)
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import type { InferNode } from "
|
|
1
|
+
import type { InferNode } from "./types";
|
|
2
2
|
import type { MessageObject, MessageValue } from "intor";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { extractInterpolationNames } from "./utils/extract-interpolation-names";
|
|
4
|
+
import { inferObject } from "./utils/infer-object";
|
|
5
|
+
import { isMessageObject } from "./utils/is-message-object";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Infer interpolation
|
|
9
|
-
*
|
|
10
|
-
* Traverses message values and extracts `{...}` interpolation names
|
|
11
|
-
* into a semantic schema tree.
|
|
8
|
+
* Infer the semantic shape of interpolation replacements from a message object.
|
|
12
9
|
*/
|
|
13
|
-
export function
|
|
10
|
+
export function inferReplacementsShape(messages: MessageObject): InferNode {
|
|
14
11
|
if (!isMessageObject(messages) || Object.keys(messages).length === 0) {
|
|
15
12
|
return { kind: "none" };
|
|
16
13
|
}
|
|
@@ -18,8 +15,6 @@ export function inferReplacementsSchema(messages: MessageObject): InferNode {
|
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
/**
|
|
21
|
-
* Infer replacement information from a single message value.
|
|
22
|
-
*
|
|
23
18
|
* - Strings are scanned for `{...}` interpolations
|
|
24
19
|
* - Objects are traversed recursively
|
|
25
20
|
* - Arrays and unsupported values are ignored
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import type { InferNode } from "
|
|
1
|
+
import type { InferNode } from "./types";
|
|
2
2
|
import type { MessageObject, MessageValue } from "intor";
|
|
3
3
|
import { tokenize, type Token } from "intor";
|
|
4
|
-
import { inferObject } from "
|
|
5
|
-
import { isMessageObject } from "
|
|
4
|
+
import { inferObject } from "./utils/infer-object";
|
|
5
|
+
import { isMessageObject } from "./utils/is-message-object";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Infer rich
|
|
9
|
-
*
|
|
10
|
-
* Traverses message values and extracts rich tag names
|
|
11
|
-
* into a semantic schema tree.
|
|
8
|
+
* Infer the semantic shape of rich tags from a message object.
|
|
12
9
|
*/
|
|
13
|
-
export function
|
|
10
|
+
export function inferRichShape(messages: MessageObject): InferNode {
|
|
14
11
|
if (!isMessageObject(messages) || Object.keys(messages).length === 0) {
|
|
15
12
|
return { kind: "none" };
|
|
16
13
|
}
|
|
@@ -18,8 +15,6 @@ export function inferRichSchema(messages: MessageObject): InferNode {
|
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
/**
|
|
21
|
-
* Infer rich tag information from a single message value.
|
|
22
|
-
*
|
|
23
18
|
* - Strings are tokenized and analyzed for rich tags
|
|
24
19
|
* - Objects are traversed recursively
|
|
25
20
|
* - Arrays and unsupported values are ignored
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { InferredShapes } from "./types";
|
|
2
|
+
import type { MessageObject } from "intor";
|
|
3
|
+
import { inferMessagesShape } from "./infer-messages-shape";
|
|
4
|
+
import { inferReplacementsShape } from "./infer-replacements-shape";
|
|
5
|
+
import { inferRichShape } from "./infer-rich-shape";
|
|
6
|
+
import { stripInternalKeys } from "./utils/strip-internal-keys";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Infer all semantic shapes from messages.
|
|
10
|
+
*/
|
|
11
|
+
export function inferShapes(messages: MessageObject): InferredShapes {
|
|
12
|
+
const inferredShapes: InferredShapes = {
|
|
13
|
+
messages: inferMessagesShape(messages),
|
|
14
|
+
replacements: inferReplacementsShape(messages),
|
|
15
|
+
rich: inferRichShape(messages),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
stripInternalKeys(inferredShapes);
|
|
19
|
+
|
|
20
|
+
return inferredShapes;
|
|
21
|
+
}
|
|
@@ -35,8 +35,8 @@ export interface InferRecordNode {
|
|
|
35
35
|
kind: "record";
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export interface
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
export interface InferredShapes {
|
|
39
|
+
messages: InferNode;
|
|
40
|
+
replacements: InferNode;
|
|
41
|
+
rich: InferNode;
|
|
42
42
|
}
|
package/src/core/{extract-usages/load-source-files-from-tscofnig.ts → scan/load-source-files.ts}
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { Project, type SourceFile } from "ts-morph";
|
|
4
|
-
import { createLogger } from "
|
|
4
|
+
import { createLogger } from "../../logger";
|
|
5
|
+
import { br, yellow } from "../../render";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Load source files from a tsconfig.
|
|
@@ -16,47 +17,65 @@ import { createLogger } from "../scan-logger";
|
|
|
16
17
|
* where the root tsconfig only contains references.
|
|
17
18
|
* - References are followed non-recursively on purpose.
|
|
18
19
|
*/
|
|
19
|
-
export function
|
|
20
|
+
export function loadSourceFiles(
|
|
20
21
|
tsconfigPath: string,
|
|
21
|
-
debug
|
|
22
|
+
debug = false,
|
|
22
23
|
): SourceFile[] {
|
|
23
|
-
|
|
24
|
+
if (debug) br();
|
|
25
|
+
const logger = createLogger(debug);
|
|
26
|
+
logger.header("Load source files - processing tsconfig", {
|
|
27
|
+
kind: "process",
|
|
28
|
+
});
|
|
29
|
+
|
|
24
30
|
// ---------------------------------------------------------------------------
|
|
25
|
-
//
|
|
31
|
+
// Try loading source files directly from the given tsconfig
|
|
26
32
|
// ---------------------------------------------------------------------------
|
|
27
33
|
const project = new Project({ tsConfigFilePath: tsconfigPath });
|
|
28
34
|
const files = project.getSourceFiles();
|
|
29
|
-
if (files.length > 0)
|
|
35
|
+
if (files.length > 0) {
|
|
36
|
+
logger.footer(`loaded ${yellow(files.length)} files`, { kind: "process" });
|
|
37
|
+
return files;
|
|
38
|
+
}
|
|
30
39
|
|
|
31
40
|
// ---------------------------------------------------------------------------
|
|
32
|
-
//
|
|
41
|
+
// No source files found, attempt to follow project references
|
|
33
42
|
// ---------------------------------------------------------------------------
|
|
34
43
|
const configDir = path.dirname(tsconfigPath);
|
|
35
|
-
const
|
|
36
|
-
|
|
44
|
+
const rawTsConfig = JSON.parse(fs.readFileSync(tsconfigPath, "utf8"));
|
|
45
|
+
|
|
46
|
+
// Project references (e.g. { references: [ { path: "./tsconfig.app.json" } ] })
|
|
47
|
+
const references: { path: string }[] = rawTsConfig.references ?? [];
|
|
48
|
+
if (references.length === 0) {
|
|
49
|
+
logger.footer("no source files found", { kind: "process" });
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
logger.log();
|
|
54
|
+
logger.process("load", `references (${references.length})`);
|
|
40
55
|
|
|
41
56
|
// ---------------------------------------------------------------------------
|
|
42
|
-
//
|
|
57
|
+
// Load source files from each referenced tsconfig
|
|
43
58
|
// ---------------------------------------------------------------------------
|
|
44
59
|
const collected: SourceFile[] = [];
|
|
45
60
|
|
|
46
61
|
for (const ref of references) {
|
|
47
|
-
const refPath = path.
|
|
62
|
+
const refPath = path.relative(configDir, ref.path);
|
|
48
63
|
|
|
49
64
|
// Skip missing referenced tsconfig files
|
|
50
65
|
if (!fs.existsSync(refPath)) {
|
|
51
|
-
|
|
66
|
+
logger.process("warn", `referenced tsconfig not found: ${refPath}`);
|
|
52
67
|
continue;
|
|
53
68
|
}
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
logger.process("load", `${path.relative(process.cwd(), refPath)}`);
|
|
56
71
|
|
|
57
72
|
const refProject = new Project({ tsConfigFilePath: refPath });
|
|
58
73
|
collected.push(...refProject.getSourceFiles());
|
|
59
74
|
}
|
|
60
75
|
|
|
76
|
+
logger.footer(`loaded ${yellow(collected.length)} files`, {
|
|
77
|
+
kind: "process",
|
|
78
|
+
lineBreakBefore: 1,
|
|
79
|
+
});
|
|
61
80
|
return collected;
|
|
62
81
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fg from "fast-glob";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PATTERNS = ["**/*.{ts,js}"];
|
|
4
|
+
|
|
5
|
+
const DEFAULT_IGNORE = [
|
|
6
|
+
"**/node_modules/**",
|
|
7
|
+
"**/dist/**",
|
|
8
|
+
"**/*.d.ts",
|
|
9
|
+
"**/*.test.*",
|
|
10
|
+
"**/*.test-d.ts",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
interface ScanFilesOptions {
|
|
14
|
+
patterns?: string[];
|
|
15
|
+
ignore?: string[];
|
|
16
|
+
cwd?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function scanFiles({
|
|
20
|
+
patterns = DEFAULT_PATTERNS,
|
|
21
|
+
ignore = DEFAULT_IGNORE,
|
|
22
|
+
cwd = process.cwd(),
|
|
23
|
+
}: ScanFilesOptions = {}) {
|
|
24
|
+
return fg(patterns, { ignore, cwd });
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ExtractedUsages } from "../../core";
|
|
2
|
+
import { dedupePreKeyUsages } from "./dedupe-pre-key-usages";
|
|
3
|
+
|
|
4
|
+
function resolveConfigKey(
|
|
5
|
+
usageConfigKey: string | undefined,
|
|
6
|
+
defaultConfigKey: string,
|
|
7
|
+
) {
|
|
8
|
+
if (usageConfigKey === "__default__") return defaultConfigKey;
|
|
9
|
+
if (usageConfigKey == null) return defaultConfigKey;
|
|
10
|
+
return usageConfigKey;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build per-config scoped usages from extracted usages
|
|
15
|
+
*/
|
|
16
|
+
export const buildScopeUsages = ({
|
|
17
|
+
usages,
|
|
18
|
+
defaultConfigKey,
|
|
19
|
+
configKey,
|
|
20
|
+
}: {
|
|
21
|
+
usages: ExtractedUsages;
|
|
22
|
+
defaultConfigKey: string;
|
|
23
|
+
configKey: string;
|
|
24
|
+
}): ExtractedUsages => {
|
|
25
|
+
const match = (u: { configKey?: string }) =>
|
|
26
|
+
resolveConfigKey(u.configKey, defaultConfigKey) === configKey;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
preKey: dedupePreKeyUsages(usages.preKey.filter((el) => match(el))),
|
|
30
|
+
key: usages.key.filter((el) => match(el)),
|
|
31
|
+
replacement: usages.replacement.filter((el) => match(el)),
|
|
32
|
+
rich: usages.rich.filter((el) => match(el)),
|
|
33
|
+
trans: usages.trans.filter((el) => match(el)),
|
|
34
|
+
};
|
|
35
|
+
};
|