@chipmobilesdk/rn-i18n 0.1.1 → 0.1.3
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/CHANGELOG.md +70 -45
- package/README.md +187 -187
- package/package.json +46 -49
- package/skills/sdk-rn-i18n/SKILL.md +1 -2
- package/src/config/types.ts +144 -152
- package/src/config/validateConfig.ts +156 -174
- package/src/format/currency.ts +27 -27
- package/src/format/date.ts +26 -26
- package/src/format/invalid.ts +13 -16
- package/src/format/number.ts +23 -25
- package/src/format/units.ts +141 -151
- package/src/i18n.ts +196 -222
- package/src/index.ts +50 -50
- package/src/locale/activeLocale.ts +46 -35
- package/src/locale/deviceLocale.ts +50 -54
- package/src/locale/persistence.ts +31 -37
- package/src/react/I18nProvider.tsx +31 -35
- package/src/react/hooks.ts +57 -61
- package/src/tooling/cli.ts +76 -78
- package/src/tooling/generateKeys.ts +62 -68
- package/src/tooling/index.ts +27 -32
- package/src/tooling/report.ts +109 -117
- package/src/tooling/runner.ts +111 -126
- package/src/tooling/scan.ts +193 -210
- package/src/tooling/sync.ts +193 -198
- package/src/tooling/types.ts +119 -119
- package/src/translation/interpolate.ts +23 -23
- package/src/translation/plurals.ts +62 -69
- package/src/translation/resolver.ts +140 -146
- package/src/translation/runtimeKeys.ts +27 -27
package/src/tooling/index.ts
CHANGED
|
@@ -1,32 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Programmatic surface of the Node-only extraction and synchronization
|
|
3
|
-
* tooling, exposed as `@chipmobilesdk/rn-i18n/tooling`. This entrypoint must
|
|
4
|
-
* never be imported from React Native code; the runtime API lives at the
|
|
5
|
-
* package root (LIB-003).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { globToRegExp, listSourceFiles, scanSource } from './scan.ts';
|
|
9
|
-
export { flattenTranslations, nestTranslations, syncLocaleFiles } from './sync.ts';
|
|
10
|
-
export { buildReport, renderReport } from './report.ts';
|
|
11
|
-
export { generateKeysFile, renderKeysModule } from './generateKeys.ts';
|
|
12
|
-
export type { GenerateKeysResult } from './generateKeys.ts';
|
|
13
|
-
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
SourceLocation,
|
|
29
|
-
SyncConfig,
|
|
30
|
-
TemplateKey,
|
|
31
|
-
TranslationTemplate,
|
|
32
|
-
} from './types.ts';
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic surface of the Node-only extraction and synchronization
|
|
3
|
+
* tooling, exposed as `@chipmobilesdk/rn-i18n/tooling`. This entrypoint must
|
|
4
|
+
* never be imported from React Native code; the runtime API lives at the
|
|
5
|
+
* package root (LIB-003).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { globToRegExp, listSourceFiles, scanSource } from './scan.ts';
|
|
9
|
+
export { flattenTranslations, nestTranslations, syncLocaleFiles } from './sync.ts';
|
|
10
|
+
export { buildReport, renderReport } from './report.ts';
|
|
11
|
+
export { generateKeysFile, renderKeysModule } from './generateKeys.ts';
|
|
12
|
+
export type { GenerateKeysResult } from './generateKeys.ts';
|
|
13
|
+
export { SyncConfigError, loadRuntimeEntries, runSync, validateSyncConfig } from './runner.ts';
|
|
14
|
+
export type { SyncRunResult } from './runner.ts';
|
|
15
|
+
export type {
|
|
16
|
+
AmbiguousUsage,
|
|
17
|
+
ExtractionReport,
|
|
18
|
+
KeyConflict,
|
|
19
|
+
LocaleSyncResult,
|
|
20
|
+
PatternConfig,
|
|
21
|
+
ReportOutcome,
|
|
22
|
+
RuntimeEntryConfig,
|
|
23
|
+
SourceLocation,
|
|
24
|
+
SyncConfig,
|
|
25
|
+
TemplateKey,
|
|
26
|
+
TranslationTemplate,
|
|
27
|
+
} from './types.ts';
|
package/src/tooling/report.ts
CHANGED
|
@@ -1,117 +1,109 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtractionReport,
|
|
3
|
-
LocaleSyncResult,
|
|
4
|
-
ReportOutcome,
|
|
5
|
-
RuntimeEntryConfig,
|
|
6
|
-
TranslationTemplate,
|
|
7
|
-
} from './types.ts';
|
|
8
|
-
|
|
9
|
-
function perLocale(
|
|
10
|
-
results: readonly LocaleSyncResult[],
|
|
11
|
-
select: (result: LocaleSyncResult) => string[],
|
|
12
|
-
): Record<string, string[]> {
|
|
13
|
-
const record: Record<string, string[]> = {};
|
|
14
|
-
for (const result of results) {
|
|
15
|
-
record[result.locale] = select(result);
|
|
16
|
-
}
|
|
17
|
-
return record;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Assembles the extraction/synchronization report distinguishing scanned
|
|
22
|
-
* template keys, runtime-only keys, stale removals, ambiguous usages, and
|
|
23
|
-
* conflicts (FR-026, FR-036).
|
|
24
|
-
*/
|
|
25
|
-
export function buildReport(
|
|
26
|
-
template: TranslationTemplate,
|
|
27
|
-
syncResults: readonly LocaleSyncResult[],
|
|
28
|
-
runtimeEntries: readonly RuntimeEntryConfig[],
|
|
29
|
-
): ExtractionReport {
|
|
30
|
-
const errors = syncResults
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
outcome
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
),
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
lines.push(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.join(' vs ');
|
|
111
|
-
lines.push(`Conflict for key "${conflict.key}": ${sources}`);
|
|
112
|
-
}
|
|
113
|
-
for (const error of report.errors) {
|
|
114
|
-
lines.push(`Error: ${error}`);
|
|
115
|
-
}
|
|
116
|
-
return lines.join('\n');
|
|
117
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
ExtractionReport,
|
|
3
|
+
LocaleSyncResult,
|
|
4
|
+
ReportOutcome,
|
|
5
|
+
RuntimeEntryConfig,
|
|
6
|
+
TranslationTemplate,
|
|
7
|
+
} from './types.ts';
|
|
8
|
+
|
|
9
|
+
function perLocale(
|
|
10
|
+
results: readonly LocaleSyncResult[],
|
|
11
|
+
select: (result: LocaleSyncResult) => string[],
|
|
12
|
+
): Record<string, string[]> {
|
|
13
|
+
const record: Record<string, string[]> = {};
|
|
14
|
+
for (const result of results) {
|
|
15
|
+
record[result.locale] = select(result);
|
|
16
|
+
}
|
|
17
|
+
return record;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Assembles the extraction/synchronization report distinguishing scanned
|
|
22
|
+
* template keys, runtime-only keys, stale removals, ambiguous usages, and
|
|
23
|
+
* conflicts (FR-026, FR-036).
|
|
24
|
+
*/
|
|
25
|
+
export function buildReport(
|
|
26
|
+
template: TranslationTemplate,
|
|
27
|
+
syncResults: readonly LocaleSyncResult[],
|
|
28
|
+
runtimeEntries: readonly RuntimeEntryConfig[],
|
|
29
|
+
): ExtractionReport {
|
|
30
|
+
const errors = syncResults.filter(result => result.error).map(result => result.error!);
|
|
31
|
+
|
|
32
|
+
let outcome: ReportOutcome = 'success';
|
|
33
|
+
if (
|
|
34
|
+
template.ambiguous.length > 0 ||
|
|
35
|
+
syncResults.some(result => result.addedKeys.length > 0 || result.runtimeReported.length > 0)
|
|
36
|
+
) {
|
|
37
|
+
outcome = 'completedWithWarnings';
|
|
38
|
+
}
|
|
39
|
+
if (errors.length > 0 || template.conflicts.length > 0) {
|
|
40
|
+
outcome = 'failed';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
outcome,
|
|
45
|
+
discovered: template.keys.map(key => ({
|
|
46
|
+
key: key.key,
|
|
47
|
+
defaultText: key.defaultText,
|
|
48
|
+
locations: key.locations.map(location => `${location.file}:${location.line}`),
|
|
49
|
+
})),
|
|
50
|
+
createdLocaleFiles: syncResults
|
|
51
|
+
.filter(result => result.created && !result.error)
|
|
52
|
+
.map(result => ({ locale: result.locale, filePath: result.filePath })),
|
|
53
|
+
addedKeys: perLocale(syncResults, result => result.addedKeys),
|
|
54
|
+
preservedKeys: perLocale(syncResults, result => result.preservedKeys),
|
|
55
|
+
removedStaleKeys: perLocale(syncResults, result => result.removedKeys),
|
|
56
|
+
missingPerLocale: perLocale(syncResults, result =>
|
|
57
|
+
[...result.addedKeys, ...result.runtimeAdded, ...result.runtimeReported].sort((a, b) =>
|
|
58
|
+
a.localeCompare(b, 'en'),
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
runtimeOnly: {
|
|
62
|
+
declared: runtimeEntries.map(entry => entry.key),
|
|
63
|
+
preserved: perLocale(syncResults, result => result.runtimePreserved),
|
|
64
|
+
added: perLocale(syncResults, result => result.runtimeAdded),
|
|
65
|
+
reported: perLocale(syncResults, result => result.runtimeReported),
|
|
66
|
+
},
|
|
67
|
+
ambiguous: template.ambiguous,
|
|
68
|
+
conflicts: template.conflicts,
|
|
69
|
+
errors,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Renders the report as readable text for CLI output. */
|
|
74
|
+
export function renderReport(report: ExtractionReport): string {
|
|
75
|
+
const lines: string[] = [];
|
|
76
|
+
lines.push(`i18n sync outcome: ${report.outcome}`);
|
|
77
|
+
lines.push(`Discovered keys: ${report.discovered.length}`);
|
|
78
|
+
for (const created of report.createdLocaleFiles) {
|
|
79
|
+
lines.push(`Created locale file: ${created.locale} -> ${created.filePath}`);
|
|
80
|
+
}
|
|
81
|
+
for (const [locale, keys] of Object.entries(report.addedKeys)) {
|
|
82
|
+
if (keys.length > 0) {
|
|
83
|
+
lines.push(`Added [${locale}]: ${keys.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const [locale, keys] of Object.entries(report.removedStaleKeys)) {
|
|
87
|
+
if (keys.length > 0) {
|
|
88
|
+
lines.push(`Removed stale [${locale}]: ${keys.join(', ')}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const [locale, keys] of Object.entries(report.runtimeOnly.reported)) {
|
|
92
|
+
if (keys.length > 0) {
|
|
93
|
+
lines.push(`Runtime-only missing [${locale}]: ${keys.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const usage of report.ambiguous) {
|
|
97
|
+
lines.push(`Ambiguous usage at ${usage.file}:${usage.line}: ${usage.snippet}`);
|
|
98
|
+
}
|
|
99
|
+
for (const conflict of report.conflicts) {
|
|
100
|
+
const sources = conflict.values
|
|
101
|
+
.map(value => `"${value.text}" (${value.location.file}:${value.location.line})`)
|
|
102
|
+
.join(' vs ');
|
|
103
|
+
lines.push(`Conflict for key "${conflict.key}": ${sources}`);
|
|
104
|
+
}
|
|
105
|
+
for (const error of report.errors) {
|
|
106
|
+
lines.push(`Error: ${error}`);
|
|
107
|
+
}
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
package/src/tooling/runner.ts
CHANGED
|
@@ -1,126 +1,111 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { scanSource } from './scan.ts';
|
|
5
|
-
import { syncLocaleFiles } from './sync.ts';
|
|
6
|
-
import { buildReport } from './report.ts';
|
|
7
|
-
import { generateKeysFile } from './generateKeys.ts';
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
throw new SyncConfigError(
|
|
65
|
-
`Runtime entries file
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
).changed;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (config.reportPath) {
|
|
116
|
-
const reportPath = path.resolve(rootDir, config.reportPath);
|
|
117
|
-
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
118
|
-
fs.writeFileSync(
|
|
119
|
-
reportPath,
|
|
120
|
-
`${JSON.stringify(report, null, '\t')}\n`,
|
|
121
|
-
'utf8',
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return { report, generatedKeysChanged };
|
|
126
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { scanSource } from './scan.ts';
|
|
5
|
+
import { syncLocaleFiles } from './sync.ts';
|
|
6
|
+
import { buildReport } from './report.ts';
|
|
7
|
+
import { generateKeysFile } from './generateKeys.ts';
|
|
8
|
+
import type { ExtractionReport, RuntimeEntryConfig, SyncConfig } from './types.ts';
|
|
9
|
+
|
|
10
|
+
export class SyncConfigError extends Error {
|
|
11
|
+
constructor(message: string) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'SyncConfigError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function validateSyncConfig(config: SyncConfig): void {
|
|
18
|
+
const issues: string[] = [];
|
|
19
|
+
if (!Array.isArray(config.sourceGlobs) || config.sourceGlobs.length === 0) {
|
|
20
|
+
issues.push('`sourceGlobs` must list at least one glob.');
|
|
21
|
+
}
|
|
22
|
+
if (!Array.isArray(config.patterns) || config.patterns.length === 0) {
|
|
23
|
+
issues.push('`patterns` must list at least one regex pattern.');
|
|
24
|
+
} else {
|
|
25
|
+
for (const pattern of config.patterns) {
|
|
26
|
+
if (!pattern.regex?.includes('(?<key>')) {
|
|
27
|
+
issues.push(`Pattern "${pattern.regex}" must contain a named (?<key>...) capture group.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!config.localesDir) {
|
|
32
|
+
issues.push('`localesDir` is required.');
|
|
33
|
+
}
|
|
34
|
+
if (!Array.isArray(config.locales) || config.locales.length === 0) {
|
|
35
|
+
issues.push('`locales` must list every supported locale.');
|
|
36
|
+
}
|
|
37
|
+
if (issues.length > 0) {
|
|
38
|
+
throw new SyncConfigError(`Invalid i18n sync configuration:\n- ${issues.join('\n- ')}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Loads declared runtime-only entries from the configured module. Accepts a
|
|
44
|
+
* default export or a named `runtimeEntries` export. The module must use
|
|
45
|
+
* type-only imports so it can run under Node's TypeScript type stripping.
|
|
46
|
+
*/
|
|
47
|
+
export async function loadRuntimeEntries(
|
|
48
|
+
rootDir: string,
|
|
49
|
+
runtimeEntriesFile: string | undefined,
|
|
50
|
+
): Promise<RuntimeEntryConfig[]> {
|
|
51
|
+
if (!runtimeEntriesFile) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const absolutePath = path.resolve(rootDir, runtimeEntriesFile);
|
|
55
|
+
if (!fs.existsSync(absolutePath)) {
|
|
56
|
+
throw new SyncConfigError(`Runtime entries file not found: ${runtimeEntriesFile}`);
|
|
57
|
+
}
|
|
58
|
+
const module = (await import(pathToFileURL(absolutePath).href)) as {
|
|
59
|
+
default?: RuntimeEntryConfig[];
|
|
60
|
+
runtimeEntries?: RuntimeEntryConfig[];
|
|
61
|
+
};
|
|
62
|
+
const entries = module.default ?? module.runtimeEntries;
|
|
63
|
+
if (!Array.isArray(entries)) {
|
|
64
|
+
throw new SyncConfigError(
|
|
65
|
+
`Runtime entries file must export an entry array (default or \`runtimeEntries\`): ${runtimeEntriesFile}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return entries;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SyncRunResult {
|
|
72
|
+
report: ExtractionReport;
|
|
73
|
+
generatedKeysChanged: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Full pipeline: scan the source tree, synchronize every locale JSON file,
|
|
78
|
+
* regenerate the typed key artifact, and assemble the report.
|
|
79
|
+
*/
|
|
80
|
+
export async function runSync(
|
|
81
|
+
rootDir: string,
|
|
82
|
+
config: SyncConfig,
|
|
83
|
+
runtimeEntriesOverride?: readonly RuntimeEntryConfig[],
|
|
84
|
+
): Promise<SyncRunResult> {
|
|
85
|
+
validateSyncConfig(config);
|
|
86
|
+
const runtimeEntries =
|
|
87
|
+
runtimeEntriesOverride ?? (await loadRuntimeEntries(rootDir, config.runtimeEntriesFile));
|
|
88
|
+
|
|
89
|
+
const template = scanSource(rootDir, config.sourceGlobs, config.patterns);
|
|
90
|
+
const syncResults = syncLocaleFiles(rootDir, config, template, runtimeEntries);
|
|
91
|
+
const report = buildReport(template, syncResults, runtimeEntries);
|
|
92
|
+
|
|
93
|
+
let generatedKeysChanged = false;
|
|
94
|
+
if (config.generateKeysFile) {
|
|
95
|
+
generatedKeysChanged = generateKeysFile(
|
|
96
|
+
rootDir,
|
|
97
|
+
config.generateKeysFile,
|
|
98
|
+
template,
|
|
99
|
+
runtimeEntries,
|
|
100
|
+
config.keysTypeName,
|
|
101
|
+
).changed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (config.reportPath) {
|
|
105
|
+
const reportPath = path.resolve(rootDir, config.reportPath);
|
|
106
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
107
|
+
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, '\t')}\n`, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { report, generatedKeysChanged };
|
|
111
|
+
}
|