@gtkx/cli 2.0.0-beta.2 → 2.0.0-beta.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/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -2
- package/dist/cli.js.map +1 -1
- package/dist/codegen/reference.d.ts.map +1 -1
- package/dist/codegen/reference.js +1 -0
- package/dist/codegen/reference.js.map +1 -1
- package/dist/codegen/run-codegen.d.ts.map +1 -1
- package/dist/codegen/run-codegen.js +2 -8
- package/dist/codegen/run-codegen.js.map +1 -1
- package/dist/codegen/store-resolver.d.ts +1 -0
- package/dist/codegen/store-resolver.d.ts.map +1 -1
- package/dist/codegen/store-resolver.js +2 -1
- package/dist/codegen/store-resolver.js.map +1 -1
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +11 -4
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +1 -0
- package/dist/commands/docs.js.map +1 -1
- package/dist/deploy/freedesktop/validate.d.ts.map +1 -1
- package/dist/deploy/freedesktop/validate.js +10 -4
- package/dist/deploy/freedesktop/validate.js.map +1 -1
- package/dist/deploy/nfpm/config.d.ts +3 -2
- package/dist/deploy/nfpm/config.d.ts.map +1 -1
- package/dist/deploy/nfpm/config.js +3 -3
- package/dist/deploy/nfpm/config.js.map +1 -1
- package/dist/deploy/node-runtime/index.d.ts.map +1 -1
- package/dist/deploy/node-runtime/index.js +2 -1
- package/dist/deploy/node-runtime/index.js.map +1 -1
- package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
- package/dist/deploy/targets/nfpm-target.js +12 -7
- package/dist/deploy/targets/nfpm-target.js.map +1 -1
- package/dist/dev/entry-env.d.ts +2 -1
- package/dist/dev/entry-env.d.ts.map +1 -1
- package/dist/dev/entry-env.js +2 -1
- package/dist/dev/entry-env.js.map +1 -1
- package/dist/dev/runner-deps.d.ts +1 -1
- package/dist/dev/runner-deps.d.ts.map +1 -1
- package/dist/dev/runner-deps.js +3 -3
- package/dist/dev/runner-deps.js.map +1 -1
- package/dist/dev/runner-main.d.ts.map +1 -1
- package/dist/dev/runner-main.js +8 -3
- package/dist/dev/runner-main.js.map +1 -1
- package/dist/dev/supervisor.d.ts +1 -0
- package/dist/dev/supervisor.d.ts.map +1 -1
- package/dist/dev/supervisor.js +4 -3
- package/dist/dev/supervisor.js.map +1 -1
- package/dist/i18n/catalogs.d.ts.map +1 -1
- package/dist/i18n/catalogs.js +1 -0
- package/dist/i18n/catalogs.js.map +1 -1
- package/dist/i18n/source-messages.d.ts.map +1 -1
- package/dist/i18n/source-messages.js +154 -36
- package/dist/i18n/source-messages.js.map +1 -1
- package/dist/internal/build-output.d.ts.map +1 -1
- package/dist/internal/build-output.js +12 -2
- package/dist/internal/build-output.js.map +1 -1
- package/dist/internal/docs-elements.d.ts +1 -0
- package/dist/internal/docs-elements.d.ts.map +1 -1
- package/dist/internal/docs-elements.js +5 -4
- package/dist/internal/docs-elements.js.map +1 -1
- package/dist/internal/errors.d.ts +2 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +1 -1
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/parent-process.d.ts +12 -0
- package/dist/internal/parent-process.d.ts.map +1 -0
- package/dist/internal/parent-process.js +74 -0
- package/dist/internal/parent-process.js.map +1 -0
- package/dist/vite-plugins/ensure-store.d.ts +1 -1
- package/dist/vite-plugins/ensure-store.d.ts.map +1 -1
- package/dist/vite-plugins/ensure-store.js +2 -2
- package/dist/vite-plugins/ensure-store.js.map +1 -1
- package/dist/vite-plugins/i18n.d.ts.map +1 -1
- package/dist/vite-plugins/i18n.js +7 -7
- package/dist/vite-plugins/i18n.js.map +1 -1
- package/dist/vite-plugins/native.d.ts.map +1 -1
- package/dist/vite-plugins/native.js +0 -4
- package/dist/vite-plugins/native.js.map +1 -1
- package/dist/vite-plugins/settings-worker-env.d.ts +1 -1
- package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
- package/dist/vite-plugins/settings-worker-env.js +2 -2
- package/dist/vite-plugins/settings-worker-env.js.map +1 -1
- package/dist/vitest-plugin.d.ts +2 -1
- package/dist/vitest-plugin.d.ts.map +1 -1
- package/dist/vitest-plugin.js +6 -6
- package/dist/vitest-plugin.js.map +1 -1
- package/package.json +10 -9
- package/src/cli.ts +8 -3
- package/src/codegen/reference.ts +1 -0
- package/src/codegen/run-codegen.ts +2 -12
- package/src/codegen/store-resolver.ts +3 -1
- package/src/commands/dev.ts +19 -4
- package/src/commands/docs.ts +1 -0
- package/src/deploy/freedesktop/validate.ts +12 -4
- package/src/deploy/nfpm/config.ts +9 -3
- package/src/deploy/node-runtime/index.ts +2 -1
- package/src/deploy/targets/nfpm-target.ts +13 -6
- package/src/dev/entry-env.ts +2 -1
- package/src/dev/runner-deps.ts +3 -3
- package/src/dev/runner-main.ts +10 -3
- package/src/dev/supervisor.ts +6 -3
- package/src/i18n/catalogs.ts +1 -0
- package/src/i18n/source-messages.ts +223 -40
- package/src/internal/build-output.ts +16 -2
- package/src/internal/docs-elements.ts +10 -4
- package/src/internal/errors.ts +1 -1
- package/src/internal/parent-process.ts +110 -0
- package/src/vite-plugins/ensure-store.ts +2 -2
- package/src/vite-plugins/i18n.ts +8 -11
- package/src/vite-plugins/native.ts +0 -5
- package/src/vite-plugins/settings-worker-env.ts +2 -2
- package/src/vitest-plugin.ts +6 -6
|
@@ -71,8 +71,9 @@ type TranslationReferences = {
|
|
|
71
71
|
calls: Set<number>;
|
|
72
72
|
elements: Set<number>;
|
|
73
73
|
entryLocations: Map<string, SourcePoint[]>;
|
|
74
|
-
locations:
|
|
74
|
+
locations: Map<string, SourcePoint>;
|
|
75
75
|
masks: SourceMask[];
|
|
76
|
+
sourcePoints: Map<number, SourcePoint>;
|
|
76
77
|
};
|
|
77
78
|
type SourceMask = { end: number; start: number };
|
|
78
79
|
type SourcePoint = { column: number; line: number };
|
|
@@ -86,6 +87,7 @@ type ImportedBinding = {
|
|
|
86
87
|
type TranslationHookBinding = { call: types.CallExpression; isCanonical: boolean };
|
|
87
88
|
type TranslationMemberExpression = types.MemberExpression | types.OptionalMemberExpression;
|
|
88
89
|
type ResolvedBinding = NonNullable<ReturnType<Scope["getBinding"]>>;
|
|
90
|
+
type LocatableNode = { loc?: { start: SourcePoint } | null | undefined };
|
|
89
91
|
|
|
90
92
|
const CONTEXT_SEPARATOR = "\u{4}";
|
|
91
93
|
const DEFAULT_NAMESPACE = "translation";
|
|
@@ -112,6 +114,98 @@ const STATIC_KEY_WRAPPER_TYPES: ReadonlySet<string> = new Set([
|
|
|
112
114
|
"TSNonNullExpression",
|
|
113
115
|
]);
|
|
114
116
|
|
|
117
|
+
class SourceExtractionError extends Error {}
|
|
118
|
+
|
|
119
|
+
const sourceErrorMessage = (error: unknown): string =>
|
|
120
|
+
error instanceof Error ? error.message : String(error);
|
|
121
|
+
|
|
122
|
+
const projectSourcePath = (root: string, path: string): string => {
|
|
123
|
+
const absolute = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
124
|
+
|
|
125
|
+
return toPosixPath(isPathInside(root, absolute) ? relative(root, absolute) : path);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const sourceExtractionError = (
|
|
129
|
+
root: string,
|
|
130
|
+
path: string,
|
|
131
|
+
point: SourcePoint | null,
|
|
132
|
+
error: unknown,
|
|
133
|
+
): SourceExtractionError => {
|
|
134
|
+
if (error instanceof SourceExtractionError) {
|
|
135
|
+
return error;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const file = projectSourcePath(root, path);
|
|
139
|
+
const location = point === null
|
|
140
|
+
? file
|
|
141
|
+
: `${file}:${String(point.line)}:${String(point.column + 1)}`;
|
|
142
|
+
|
|
143
|
+
return new SourceExtractionError(`${location}: ${sourceErrorMessage(error)}`, { cause: error });
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const locatablePoint = (node: LocatableNode): SourcePoint | null => {
|
|
147
|
+
const location = node.loc?.start;
|
|
148
|
+
|
|
149
|
+
return location === undefined ? null : { column: location.column, line: location.line };
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const isSourcePoint = (value: unknown): value is SourcePoint =>
|
|
153
|
+
typeof value === "object" &&
|
|
154
|
+
value !== null &&
|
|
155
|
+
"column" in value &&
|
|
156
|
+
typeof value.column === "number" &&
|
|
157
|
+
"line" in value &&
|
|
158
|
+
typeof value.line === "number";
|
|
159
|
+
|
|
160
|
+
const errorSourcePoint = (error: unknown): SourcePoint | null => {
|
|
161
|
+
if (typeof error !== "object" || error === null) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ("loc" in error && isSourcePoint(error.loc)) {
|
|
166
|
+
return error.loc;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return "location" in error && isSourcePoint(error.location) ? error.location : null;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const atSourceNode = <T>(
|
|
173
|
+
root: string,
|
|
174
|
+
path: string,
|
|
175
|
+
node: LocatableNode,
|
|
176
|
+
operation: () => T,
|
|
177
|
+
): T => {
|
|
178
|
+
try {
|
|
179
|
+
return operation();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw sourceExtractionError(root, path, locatablePoint(node), error);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const atExtractedLocation = <T>(
|
|
186
|
+
root: string,
|
|
187
|
+
locations: SourceLocation[],
|
|
188
|
+
operation: () => T,
|
|
189
|
+
): T => {
|
|
190
|
+
const location = locations.find((candidate) =>
|
|
191
|
+
candidate.line !== undefined && candidate.column !== undefined);
|
|
192
|
+
|
|
193
|
+
if (location?.line === undefined || location.column === undefined) {
|
|
194
|
+
return operation();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
return operation();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw sourceExtractionError(
|
|
201
|
+
root,
|
|
202
|
+
location.file,
|
|
203
|
+
{ column: location.column, line: location.line },
|
|
204
|
+
error,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
115
209
|
const quietLogger = (): { logger: Logger; reports: string[] } => {
|
|
116
210
|
const reports: string[] = [];
|
|
117
211
|
|
|
@@ -223,11 +317,15 @@ const expandedPlural = (entry: ExtractedKey): { baseKey: string; variant: Plural
|
|
|
223
317
|
};
|
|
224
318
|
};
|
|
225
319
|
|
|
226
|
-
const groupedPlurals = (entries: ExtractedKey[]): PluralGroup[] => {
|
|
320
|
+
const groupedPlurals = (root: string, entries: ExtractedKey[]): PluralGroup[] => {
|
|
227
321
|
const groups: Map<string, PluralGroup> = new Map();
|
|
228
322
|
|
|
229
323
|
for (const entry of entries) {
|
|
230
|
-
const { baseKey, variant } =
|
|
324
|
+
const { baseKey, variant } = atExtractedLocation(
|
|
325
|
+
root,
|
|
326
|
+
entry.locations ?? [],
|
|
327
|
+
() => expandedPlural(entry),
|
|
328
|
+
);
|
|
231
329
|
const namespace = namespaceFor(entry);
|
|
232
330
|
const key = `${namespace}\0${baseKey}`;
|
|
233
331
|
const group = groups.get(key) ?? { baseKey, namespace, variants: [] };
|
|
@@ -276,8 +374,13 @@ const pluralMessage = ({ baseKey, namespace, variants }: PluralGroup): SourceMes
|
|
|
276
374
|
};
|
|
277
375
|
};
|
|
278
376
|
|
|
279
|
-
const pluralMessages = (entries: ExtractedKey[]): SourceMessage[] =>
|
|
280
|
-
groupedPlurals(entries).map((group) =>
|
|
377
|
+
const pluralMessages = (root: string, entries: ExtractedKey[]): SourceMessage[] =>
|
|
378
|
+
groupedPlurals(root, entries).map((group) =>
|
|
379
|
+
atExtractedLocation(
|
|
380
|
+
root,
|
|
381
|
+
group.variants.flatMap((variant) => variant.locations),
|
|
382
|
+
() => pluralMessage(group),
|
|
383
|
+
));
|
|
281
384
|
|
|
282
385
|
const assertCardinal = (entry: ExtractedKey): void => {
|
|
283
386
|
if (entry.isOrdinal === true) {
|
|
@@ -285,12 +388,14 @@ const assertCardinal = (entry: ExtractedKey): void => {
|
|
|
285
388
|
}
|
|
286
389
|
};
|
|
287
390
|
|
|
288
|
-
const sourceMessages = (entries: ExtractedKey[]): SourceMessage[] => {
|
|
391
|
+
const sourceMessages = (root: string, entries: ExtractedKey[]): SourceMessage[] => {
|
|
289
392
|
const points: ExtractedKey[] = [];
|
|
290
393
|
const plurals: ExtractedKey[] = [];
|
|
291
394
|
|
|
292
395
|
for (const entry of entries) {
|
|
293
|
-
|
|
396
|
+
atExtractedLocation(root, entry.locations ?? [], () => {
|
|
397
|
+
assertCardinal(entry);
|
|
398
|
+
});
|
|
294
399
|
|
|
295
400
|
if (entry.isExpandedPlural === true) {
|
|
296
401
|
plurals.push(entry);
|
|
@@ -304,7 +409,11 @@ const sourceMessages = (entries: ExtractedKey[]): SourceMessage[] => {
|
|
|
304
409
|
points.push(entry);
|
|
305
410
|
}
|
|
306
411
|
|
|
307
|
-
return [
|
|
412
|
+
return [
|
|
413
|
+
...points.map((entry) =>
|
|
414
|
+
atExtractedLocation(root, entry.locations ?? [], () => pointMessage(entry))),
|
|
415
|
+
...pluralMessages(root, plurals),
|
|
416
|
+
];
|
|
308
417
|
};
|
|
309
418
|
|
|
310
419
|
const compareMessages = (left: SourceMessage, right: SourceMessage): number =>
|
|
@@ -745,16 +854,14 @@ const registerSourceMask = (node: types.Node, references: TranslationReferences)
|
|
|
745
854
|
const sourceLocationKey = (line: number, column: number): string => `${String(line)}:${String(column)}`;
|
|
746
855
|
|
|
747
856
|
const sourcePoint = (node: types.Node): SourcePoint | null => {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
return location === undefined ? null : { column: location.column, line: location.line };
|
|
857
|
+
return locatablePoint(node);
|
|
751
858
|
};
|
|
752
859
|
|
|
753
860
|
const registerSourceLocation = (node: types.Node, references: TranslationReferences): SourcePoint | null => {
|
|
754
861
|
const point = sourcePoint(node);
|
|
755
862
|
|
|
756
863
|
if (point !== null) {
|
|
757
|
-
references.locations.
|
|
864
|
+
references.locations.set(sourceLocationKey(point.line, point.column), point);
|
|
758
865
|
}
|
|
759
866
|
|
|
760
867
|
return point;
|
|
@@ -901,6 +1008,11 @@ const registerCallReference = (
|
|
|
901
1008
|
references.calls.add(nodeStart(callPath));
|
|
902
1009
|
const firstArgument = callPath.node.arguments[0];
|
|
903
1010
|
const point = registerSourceLocation(firstArgument ?? callPath.node, references);
|
|
1011
|
+
|
|
1012
|
+
if (point !== null) {
|
|
1013
|
+
references.sourcePoints.set(nodeStart(callPath), point);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
904
1016
|
registerEntryLocation(extractedEntryKey(callPath.node, keyPrefix), point, references);
|
|
905
1017
|
};
|
|
906
1018
|
|
|
@@ -910,6 +1022,26 @@ const parserPlugins = (lang: ReturnType<typeof sourceLanguage>): ("decorators" |
|
|
|
910
1022
|
...(lang === "jsx" || lang === "tsx" ? ["jsx" as const] : []),
|
|
911
1023
|
];
|
|
912
1024
|
|
|
1025
|
+
const parseTranslationReferences = (
|
|
1026
|
+
root: string,
|
|
1027
|
+
path: string,
|
|
1028
|
+
source: string,
|
|
1029
|
+
lang: ReturnType<typeof sourceLanguage>,
|
|
1030
|
+
) => {
|
|
1031
|
+
try {
|
|
1032
|
+
return parseBabelSync(source, {
|
|
1033
|
+
ast: true,
|
|
1034
|
+
babelrc: false,
|
|
1035
|
+
configFile: false,
|
|
1036
|
+
filename: projectSourcePath(root, path),
|
|
1037
|
+
parserOpts: { createParenthesizedExpressions: true, plugins: parserPlugins(lang) },
|
|
1038
|
+
sourceType: "module",
|
|
1039
|
+
});
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
throw sourceExtractionError(root, path, errorSourcePoint(error), error);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
|
|
913
1045
|
const registerIdentifierTranslationCall = (
|
|
914
1046
|
callPath: NodePath<types.CallExpression>,
|
|
915
1047
|
callee: NodePath,
|
|
@@ -1010,6 +1142,19 @@ const registerUnrecognizedElementMasks = (
|
|
|
1010
1142
|
}
|
|
1011
1143
|
};
|
|
1012
1144
|
|
|
1145
|
+
const registerElementReference = (
|
|
1146
|
+
elementPath: NodePath,
|
|
1147
|
+
references: TranslationReferences,
|
|
1148
|
+
): void => {
|
|
1149
|
+
const start = nodeStart(elementPath);
|
|
1150
|
+
const point = registerSourceLocation(elementPath.node, references);
|
|
1151
|
+
references.elements.add(start);
|
|
1152
|
+
|
|
1153
|
+
if (point !== null) {
|
|
1154
|
+
references.sourcePoints.set(start, point);
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1013
1158
|
const registerTranslationElement = (elementPath: NodePath, references: TranslationReferences): void => {
|
|
1014
1159
|
const name = elementPath.get("name");
|
|
1015
1160
|
|
|
@@ -1024,8 +1169,7 @@ const registerTranslationElement = (elementPath: NodePath, references: Translati
|
|
|
1024
1169
|
}
|
|
1025
1170
|
|
|
1026
1171
|
if (kind === "canonical") {
|
|
1027
|
-
|
|
1028
|
-
registerSourceLocation(elementPath.node, references);
|
|
1172
|
+
registerElementReference(elementPath, references);
|
|
1029
1173
|
} else if (TRANS_ELEMENTS.has(name.node.name)) {
|
|
1030
1174
|
registerUnrecognizedElementMasks(elementPath, name, references);
|
|
1031
1175
|
}
|
|
@@ -1043,18 +1187,12 @@ const maskUnrecognizedTranslations = (source: string, references: TranslationRef
|
|
|
1043
1187
|
};
|
|
1044
1188
|
|
|
1045
1189
|
const translationReferences = (
|
|
1190
|
+
root: string,
|
|
1046
1191
|
path: string,
|
|
1047
1192
|
source: string,
|
|
1048
1193
|
lang: ReturnType<typeof sourceLanguage>,
|
|
1049
1194
|
): TranslationReferences => {
|
|
1050
|
-
const parsed =
|
|
1051
|
-
ast: true,
|
|
1052
|
-
babelrc: false,
|
|
1053
|
-
configFile: false,
|
|
1054
|
-
filename: path,
|
|
1055
|
-
parserOpts: { createParenthesizedExpressions: true, plugins: parserPlugins(lang) },
|
|
1056
|
-
sourceType: "module",
|
|
1057
|
-
});
|
|
1195
|
+
const parsed = parseTranslationReferences(root, path, source, lang);
|
|
1058
1196
|
|
|
1059
1197
|
if (parsed === null) {
|
|
1060
1198
|
throw new Error(`Translation extraction could not parse ${path}`);
|
|
@@ -1064,22 +1202,31 @@ const translationReferences = (
|
|
|
1064
1202
|
calls: new Set(),
|
|
1065
1203
|
elements: new Set(),
|
|
1066
1204
|
entryLocations: new Map(),
|
|
1067
|
-
locations: new
|
|
1205
|
+
locations: new Map(),
|
|
1068
1206
|
masks: [],
|
|
1207
|
+
sourcePoints: new Map(),
|
|
1069
1208
|
};
|
|
1070
1209
|
|
|
1071
1210
|
traverse(parsed, {
|
|
1072
1211
|
CallExpression(callPath) {
|
|
1073
|
-
|
|
1212
|
+
atSourceNode(root, path, callPath.node, () => {
|
|
1213
|
+
registerTranslationCall(callPath, references);
|
|
1214
|
+
});
|
|
1074
1215
|
},
|
|
1075
1216
|
JSXOpeningElement(elementPath) {
|
|
1076
|
-
|
|
1217
|
+
atSourceNode(root, path, elementPath.node, () => {
|
|
1218
|
+
registerTranslationElement(elementPath, references);
|
|
1219
|
+
});
|
|
1077
1220
|
},
|
|
1078
1221
|
OptionalCallExpression(callPath) {
|
|
1079
|
-
|
|
1222
|
+
atSourceNode(root, path, callPath.node, () => {
|
|
1223
|
+
assertSupportedOptionalCall(callPath);
|
|
1224
|
+
});
|
|
1080
1225
|
},
|
|
1081
1226
|
TaggedTemplateExpression(tagPath) {
|
|
1082
|
-
|
|
1227
|
+
atSourceNode(root, path, tagPath.node, () => {
|
|
1228
|
+
assertUnsupportedTranslationTag(tagPath);
|
|
1229
|
+
});
|
|
1083
1230
|
},
|
|
1084
1231
|
});
|
|
1085
1232
|
|
|
@@ -1165,22 +1312,44 @@ const assertSupportedTransElement = (node: ESTree.JSXOpeningElement, references:
|
|
|
1165
1312
|
}
|
|
1166
1313
|
};
|
|
1167
1314
|
|
|
1168
|
-
const assertSupportedTranslationSyntax = (path: string, source: string): TranslationReferences => {
|
|
1315
|
+
const assertSupportedTranslationSyntax = (root: string, path: string, source: string): TranslationReferences => {
|
|
1169
1316
|
const lang = sourceLanguage(path);
|
|
1170
1317
|
|
|
1171
1318
|
if (lang === undefined) {
|
|
1172
|
-
return {
|
|
1319
|
+
return {
|
|
1320
|
+
calls: new Set(),
|
|
1321
|
+
elements: new Set(),
|
|
1322
|
+
entryLocations: new Map(),
|
|
1323
|
+
locations: new Map(),
|
|
1324
|
+
masks: [],
|
|
1325
|
+
sourcePoints: new Map(),
|
|
1326
|
+
};
|
|
1173
1327
|
}
|
|
1174
1328
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1329
|
+
let parsed: ReturnType<typeof parseSync>;
|
|
1330
|
+
|
|
1331
|
+
try {
|
|
1332
|
+
parsed = parseSync(projectSourcePath(root, path), source, { lang });
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
throw sourceExtractionError(root, path, errorSourcePoint(error), error);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const references = translationReferences(root, path, source, lang);
|
|
1177
1338
|
|
|
1178
1339
|
new Visitor({
|
|
1179
1340
|
CallExpression(node) {
|
|
1180
|
-
|
|
1341
|
+
try {
|
|
1342
|
+
assertSupportedTranslationCall(node, references);
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
throw sourceExtractionError(root, path, references.sourcePoints.get(node.start) ?? null, error);
|
|
1345
|
+
}
|
|
1181
1346
|
},
|
|
1182
1347
|
JSXOpeningElement(node) {
|
|
1183
|
-
|
|
1348
|
+
try {
|
|
1349
|
+
assertSupportedTransElement(node, references);
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
throw sourceExtractionError(root, path, references.sourcePoints.get(node.start) ?? null, error);
|
|
1352
|
+
}
|
|
1184
1353
|
},
|
|
1185
1354
|
}).visit(parsed.program);
|
|
1186
1355
|
|
|
@@ -1476,12 +1645,19 @@ const extractedSourcePoints = (root: string, entries: ExtractedKeysMap): Set<str
|
|
|
1476
1645
|
return points;
|
|
1477
1646
|
};
|
|
1478
1647
|
|
|
1479
|
-
const
|
|
1648
|
+
const missingSourcePoint = (
|
|
1480
1649
|
file: string,
|
|
1481
1650
|
references: TranslationReferences,
|
|
1482
1651
|
extracted: Set<string>,
|
|
1483
|
-
):
|
|
1484
|
-
references.locations
|
|
1652
|
+
): SourcePoint | null => {
|
|
1653
|
+
for (const [location, point] of references.locations) {
|
|
1654
|
+
if (!extracted.has(`${file}\0${location}`)) {
|
|
1655
|
+
return point;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
return null;
|
|
1660
|
+
};
|
|
1485
1661
|
|
|
1486
1662
|
const assertAllReferencesExtracted = (
|
|
1487
1663
|
root: string,
|
|
@@ -1491,8 +1667,15 @@ const assertAllReferencesExtracted = (
|
|
|
1491
1667
|
const extracted = extractedSourcePoints(root, entries);
|
|
1492
1668
|
|
|
1493
1669
|
for (const [file, sourceReferences] of references) {
|
|
1494
|
-
|
|
1495
|
-
|
|
1670
|
+
const point = missingSourcePoint(file, sourceReferences, extracted);
|
|
1671
|
+
|
|
1672
|
+
if (point !== null) {
|
|
1673
|
+
throw sourceExtractionError(
|
|
1674
|
+
root,
|
|
1675
|
+
file,
|
|
1676
|
+
point,
|
|
1677
|
+
new Error("A translation expression could not be extracted safely"),
|
|
1678
|
+
);
|
|
1496
1679
|
}
|
|
1497
1680
|
}
|
|
1498
1681
|
};
|
|
@@ -1508,7 +1691,7 @@ const findSourceMessages = async (root: string, sourceFiles: string[]): Promise<
|
|
|
1508
1691
|
const references: Map<string, TranslationReferences> = new Map();
|
|
1509
1692
|
|
|
1510
1693
|
for (const [file, source] of sources) {
|
|
1511
|
-
references.set(file, assertSupportedTranslationSyntax(file, source));
|
|
1694
|
+
references.set(file, assertSupportedTranslationSyntax(root, file, source));
|
|
1512
1695
|
}
|
|
1513
1696
|
|
|
1514
1697
|
let extracted: ExtractedKeysMap | undefined;
|
|
@@ -1542,7 +1725,7 @@ const findSourceMessages = async (root: string, sourceFiles: string[]): Promise<
|
|
|
1542
1725
|
throw new Error(reports.join("\n") || "i18next extraction failed");
|
|
1543
1726
|
}
|
|
1544
1727
|
|
|
1545
|
-
return sourceMessages(extracted.values().toArray())
|
|
1728
|
+
return sourceMessages(root, extracted.values().toArray())
|
|
1546
1729
|
.map((message) => inferLocations(message, sources))
|
|
1547
1730
|
.toSorted(compareMessages);
|
|
1548
1731
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isPathInside, isRecord } from "@gtkx/utils";
|
|
2
2
|
import { lstatSync, readdirSync } from "node:fs";
|
|
3
|
-
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
BUILD_MANIFEST_FILENAME,
|
|
6
6
|
BUILD_MANIFEST_GENERATOR,
|
|
@@ -56,8 +56,22 @@ const isReusableBuildDirectory = (path: string): boolean => {
|
|
|
56
56
|
const buildOutputError = (root: string): Error =>
|
|
57
57
|
new Error(`Build output must be an empty directory or an earlier GTKX build below the project root ${root}`);
|
|
58
58
|
|
|
59
|
+
const hasGtkxBuildAncestor = (root: string, outDir: string): boolean => {
|
|
60
|
+
let ancestor = dirname(outDir);
|
|
61
|
+
|
|
62
|
+
while (isPathInside(root, ancestor)) {
|
|
63
|
+
if (isGtkxBuildDirectory(ancestor)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ancestor = dirname(ancestor);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return false;
|
|
71
|
+
};
|
|
72
|
+
|
|
59
73
|
const assertSafeBuildLocation = (root: string, outDir: string): void => {
|
|
60
|
-
if (!isPathInside(root, outDir) || hasSymlinkComponent(root, outDir)) {
|
|
74
|
+
if (!isPathInside(root, outDir) || hasSymlinkComponent(root, outDir) || hasGtkxBuildAncestor(root, outDir)) {
|
|
61
75
|
throw buildOutputError(root);
|
|
62
76
|
}
|
|
63
77
|
};
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ElementProps,
|
|
3
|
+
type OmittedProps,
|
|
4
|
+
resolveStore,
|
|
5
|
+
} from "@gtkx/codegen";
|
|
6
|
+
import { readBuiltinElementsForDocs } from "@gtkx/codegen/internal";
|
|
2
7
|
import { existsSync } from "node:fs";
|
|
3
8
|
|
|
4
9
|
type DocsElements = {
|
|
5
10
|
props: ElementProps;
|
|
6
11
|
omittedProps: OmittedProps;
|
|
12
|
+
acceptedChildTypes: Record<string, string[]>;
|
|
7
13
|
};
|
|
8
14
|
|
|
9
15
|
const resolveDocsElements = async (cwd: string): Promise<DocsElements> => {
|
|
10
16
|
const { gi } = resolveStore(cwd);
|
|
11
17
|
|
|
12
18
|
if (!existsSync(gi.storeDir)) {
|
|
13
|
-
return { props: {}, omittedProps: {} };
|
|
19
|
+
return { props: {}, omittedProps: {}, acceptedChildTypes: {} };
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
const { props, omittedProps } = await
|
|
22
|
+
const { props, omittedProps, acceptedChildTypes } = await readBuiltinElementsForDocs();
|
|
17
23
|
|
|
18
|
-
return { props, omittedProps };
|
|
24
|
+
return { props, omittedProps, acceptedChildTypes };
|
|
19
25
|
};
|
|
20
26
|
|
|
21
27
|
export { resolveDocsElements };
|
package/src/internal/errors.ts
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
type ProcessIdentity = {
|
|
4
|
+
pid: number;
|
|
5
|
+
parentId: number;
|
|
6
|
+
processGroupId: number;
|
|
7
|
+
startTime: string;
|
|
8
|
+
state: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type ProcessGroupOwner = Pick<ProcessIdentity, "pid" | "startTime">;
|
|
12
|
+
type ProcessGroupOwnerCapture = { isCaptured: true; owner: ProcessGroupOwner | undefined } |
|
|
13
|
+
{ isCaptured: false; error: Error };
|
|
14
|
+
|
|
15
|
+
const initialParentId = process.ppid;
|
|
16
|
+
const STOPPED_PROCESS_STATES: Set<string> = new Set(["Z", "X", "x"]);
|
|
17
|
+
|
|
18
|
+
const processIdentity = (pid: number): ProcessIdentity => {
|
|
19
|
+
const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
|
|
20
|
+
const fields = stat.slice(stat.lastIndexOf(") ") + 2).split(" ");
|
|
21
|
+
const state = fields[0];
|
|
22
|
+
const parentId = Number(fields[1]);
|
|
23
|
+
const processGroupId = Number(fields[2]);
|
|
24
|
+
const startTime = fields[19];
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
state === undefined ||
|
|
28
|
+
startTime === undefined ||
|
|
29
|
+
!Number.isSafeInteger(parentId) ||
|
|
30
|
+
!Number.isSafeInteger(processGroupId)
|
|
31
|
+
) {
|
|
32
|
+
throw new Error(`Failed to identify process ${String(pid)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { pid, parentId, processGroupId, startTime, state };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const ancestorFor = (process: ProcessIdentity, ancestorId: number): ProcessIdentity | undefined => {
|
|
39
|
+
let current = process;
|
|
40
|
+
|
|
41
|
+
while (current.parentId > 1) {
|
|
42
|
+
const parent = processIdentity(current.parentId);
|
|
43
|
+
|
|
44
|
+
if (parent.pid === ancestorId) {
|
|
45
|
+
return parent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
current = parent;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const processGroupOwner = (process: ProcessIdentity): ProcessGroupOwner => {
|
|
55
|
+
if (process.processGroupId !== process.pid || STOPPED_PROCESS_STATES.has(process.state)) {
|
|
56
|
+
throw new Error(`Failed to identify process group ${String(process.pid)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { pid: process.pid, startTime: process.startTime };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const processGroupOwnerFor = (process: ProcessIdentity): ProcessGroupOwner | undefined => {
|
|
63
|
+
if (process.processGroupId === process.pid) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const groupLeader = processGroupOwner(processIdentity(process.processGroupId));
|
|
68
|
+
const owner = ancestorFor(process, groupLeader.pid);
|
|
69
|
+
|
|
70
|
+
if (owner !== undefined) {
|
|
71
|
+
return groupLeader;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const currentGroupLeader = processGroupOwner(processIdentity(groupLeader.pid));
|
|
75
|
+
|
|
76
|
+
if (currentGroupLeader.startTime !== groupLeader.startTime) {
|
|
77
|
+
throw new Error(`Failed to identify process group ${String(groupLeader.pid)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return undefined;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const captureProcessGroupOwner = (): ProcessGroupOwnerCapture => {
|
|
84
|
+
try {
|
|
85
|
+
const initialProcess = processIdentity(process.pid);
|
|
86
|
+
|
|
87
|
+
if (initialProcess.parentId !== initialParentId) {
|
|
88
|
+
throw new Error("The process that launched GTKX exited during startup");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { isCaptured: true, owner: processGroupOwnerFor(initialProcess) };
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
isCaptured: false,
|
|
95
|
+
error: error instanceof Error ? error : new Error("Failed to identify the process that launched GTKX"),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const capturedProcessGroupOwner = captureProcessGroupOwner();
|
|
101
|
+
|
|
102
|
+
const getInitialProcessGroupOwner = (): ProcessGroupOwner | undefined => {
|
|
103
|
+
if (!capturedProcessGroupOwner.isCaptured) {
|
|
104
|
+
throw capturedProcessGroupOwner.error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return capturedProcessGroupOwner.owner;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export { getInitialProcessGroupOwner, initialParentId };
|
|
@@ -3,11 +3,11 @@ import { ensureGenerated } from "../codegen/run-codegen.js";
|
|
|
3
3
|
|
|
4
4
|
const TEST_MODE = "test";
|
|
5
5
|
|
|
6
|
-
const gtkxEnsureStore = (): Plugin => ({
|
|
6
|
+
const gtkxEnsureStore = (configFile?: string): Plugin => ({
|
|
7
7
|
name: "gtkx:ensure-store",
|
|
8
8
|
|
|
9
9
|
async config() {
|
|
10
|
-
await ensureGenerated(process.cwd(), { shouldAnnounce: true, mode: TEST_MODE });
|
|
10
|
+
await ensureGenerated(process.cwd(), { shouldAnnounce: true, mode: TEST_MODE, configFile });
|
|
11
11
|
},
|
|
12
12
|
});
|
|
13
13
|
|
package/src/vite-plugins/i18n.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ConfigLoader } from "@gtkx/config";
|
|
2
2
|
import type { Plugin, ResolvedConfig } from "vite";
|
|
3
3
|
import { createConfigLoader } from "@gtkx/config/internal";
|
|
4
|
-
import { isPathWithin, toPosixPath } from "@gtkx/utils";
|
|
4
|
+
import { isPathWithin, logger, toPosixPath } from "@gtkx/utils";
|
|
5
5
|
import { existsSync, mkdirSync } from "node:fs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { dirname, join, posix, relative, resolve } from "node:path";
|
|
@@ -23,6 +23,7 @@ type I18nState = {
|
|
|
23
23
|
outDir: string;
|
|
24
24
|
project: CatalogProject | null;
|
|
25
25
|
extraction: Promise<void>;
|
|
26
|
+
hotUpdateTimestamp: number | null;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
const BOOTSTRAP_SPECIFIER = "@gtkx/i18n/bootstrap";
|
|
@@ -159,12 +160,11 @@ const isProjectSource = (state: I18nState, path: string): boolean => {
|
|
|
159
160
|
const refreshProjectMessages = async (
|
|
160
161
|
state: I18nState,
|
|
161
162
|
shouldPreserveMetadataMessages: boolean,
|
|
162
|
-
reportError: (message: string) => void,
|
|
163
163
|
): Promise<void> => {
|
|
164
164
|
try {
|
|
165
165
|
await queueExtraction(state, shouldPreserveMetadataMessages, false);
|
|
166
166
|
} catch (error) {
|
|
167
|
-
|
|
167
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
168
168
|
}
|
|
169
169
|
};
|
|
170
170
|
|
|
@@ -176,6 +176,7 @@ const gtkxI18n = (
|
|
|
176
176
|
const state: I18nState = {
|
|
177
177
|
entryPath,
|
|
178
178
|
extraction: Promise.resolve(),
|
|
179
|
+
hotUpdateTimestamp: null,
|
|
179
180
|
i18nRoot: "",
|
|
180
181
|
outDir: "",
|
|
181
182
|
project: null,
|
|
@@ -190,17 +191,13 @@ const gtkxI18n = (
|
|
|
190
191
|
buildStart: () => queueExtraction(state, shouldPreserveMetadataMessages, true),
|
|
191
192
|
|
|
192
193
|
hotUpdate(options) {
|
|
193
|
-
if (!isProjectSource(state, options.file)) {
|
|
194
|
+
if (!isProjectSource(state, options.file) || state.hotUpdateTimestamp === options.timestamp) {
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
(message) => {
|
|
201
|
-
options.server.config.logger.error(message);
|
|
202
|
-
},
|
|
203
|
-
);
|
|
198
|
+
state.hotUpdateTimestamp = options.timestamp;
|
|
199
|
+
|
|
200
|
+
return refreshProjectMessages(state, shouldPreserveMetadataMessages);
|
|
204
201
|
},
|
|
205
202
|
|
|
206
203
|
transform(code, id) {
|