@factorialco/fcode-cli 3.4.0 → 3.5.1
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/README.md +1 -1
- package/dist/core/executors/javascript-execution-task.js +21 -8
- package/dist/core/resource-pusher.js +5 -1
- package/dist/runtime/libs/python/fcode/context.py +28 -8
- package/dist/services/fcode-graphql-client.js +48 -13
- package/dist/services/locale-resolver.d.ts +6 -2
- package/dist/services/locale-resolver.js +29 -6
- package/dist/utils/file-parameter-warnings.d.ts +8 -0
- package/dist/utils/file-parameter-warnings.js +55 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ $ npm install -g @factorialco/fcode-cli
|
|
|
20
20
|
$ fcode COMMAND
|
|
21
21
|
running command...
|
|
22
22
|
$ fcode (--version)
|
|
23
|
-
@factorialco/fcode-cli/3.
|
|
23
|
+
@factorialco/fcode-cli/3.5.1 linux-x64 node-v22.22.3
|
|
24
24
|
$ fcode --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ fcode COMMAND
|
|
@@ -606,25 +606,38 @@ const main = typeof _hooksModule['${fn}'] === 'function'
|
|
|
606
606
|
i18n: Object.assign((key, args, options) => {
|
|
607
607
|
const translations = ${JSON.stringify(metadata.i18n.translations, null, 2)};
|
|
608
608
|
const byLocale = ${JSON.stringify(metadata.i18n.byLocale, null, 2)};
|
|
609
|
+
const primaryLocale = ${JSON.stringify(metadata.i18n.primaryLocale ?? null)};
|
|
609
610
|
const requested = options && typeof options === 'object' && options.locale !== undefined && options.locale !== null
|
|
610
611
|
? String(options.locale)
|
|
611
612
|
: undefined;
|
|
612
613
|
// An identifier no locale matches is shortened one segment at a time ('en-gb' reads
|
|
613
|
-
// 'en'), by name first and ignoring case only as a last resort
|
|
614
|
-
//
|
|
614
|
+
// 'en'), by name first and ignoring case only as a last resort, and a language that
|
|
615
|
+
// still matches nothing is broadened to a regional locale of it ('es' reads 'es-ES',
|
|
616
|
+
// the primary one if it is among them, else the alphabetically first) — the same chain
|
|
617
|
+
// the cloud resolves, so a local run answers the same identifiers the same way.
|
|
618
|
+
const names = Object.keys(byLocale);
|
|
619
|
+
const matching = (candidate) => names.includes(candidate)
|
|
620
|
+
? candidate
|
|
621
|
+
: names.filter((name) => name.toLowerCase() === candidate.toLowerCase()).sort()[0];
|
|
615
622
|
const localeMaps = [];
|
|
616
|
-
|
|
617
|
-
const names = Object.keys(byLocale);
|
|
618
|
-
const lowered = candidate.toLowerCase();
|
|
619
|
-
const match = names.includes(candidate)
|
|
620
|
-
? candidate
|
|
621
|
-
: names.filter((name) => name.toLowerCase() === lowered).sort()[0];
|
|
623
|
+
const add = (match) => {
|
|
622
624
|
if (match !== undefined && !localeMaps.includes(byLocale[match])) {
|
|
623
625
|
localeMaps.push(byLocale[match]);
|
|
624
626
|
}
|
|
627
|
+
};
|
|
628
|
+
let language;
|
|
629
|
+
for (let candidate = requested; candidate; ) {
|
|
630
|
+
add(matching(candidate));
|
|
631
|
+
language = candidate;
|
|
625
632
|
const lastSeparator = Math.max(candidate.lastIndexOf('-'), candidate.lastIndexOf('_'));
|
|
626
633
|
candidate = lastSeparator > 0 ? candidate.slice(0, lastSeparator) : undefined;
|
|
627
634
|
}
|
|
635
|
+
if (language !== undefined && !/[-_]/.test(language) && matching(language) === undefined) {
|
|
636
|
+
const sameLanguage = names
|
|
637
|
+
.filter((name) => name.split(/[-_]/)[0].toLowerCase() === language.toLowerCase())
|
|
638
|
+
.sort();
|
|
639
|
+
add(sameLanguage.includes(primaryLocale) ? primaryLocale : sameLanguage[0]);
|
|
640
|
+
}
|
|
628
641
|
let template;
|
|
629
642
|
for (const localeMap of localeMaps) {
|
|
630
643
|
template = Object.hasOwn(localeMap, key) ? localeMap[key] : undefined;
|
|
@@ -6,6 +6,7 @@ const resource_checker_1 = require("../services/resource-checker");
|
|
|
6
6
|
const promise_1 = require("../utils/promise");
|
|
7
7
|
const types_1 = require("./types");
|
|
8
8
|
const variable_warnings_1 = require("../utils/variable-warnings");
|
|
9
|
+
const file_parameter_warnings_1 = require("../utils/file-parameter-warnings");
|
|
9
10
|
const global_1 = require("../config/global");
|
|
10
11
|
const settings_1 = require("../config/settings");
|
|
11
12
|
class ResourcePusher {
|
|
@@ -64,7 +65,10 @@ class ResourcePusher {
|
|
|
64
65
|
async pushResource(resource) {
|
|
65
66
|
try {
|
|
66
67
|
if (this._repository._repositoryName === 'processes') {
|
|
67
|
-
const
|
|
68
|
+
const process = resource;
|
|
69
|
+
// Only the processes being pushed are named, so each gets fixed by whoever next touches it.
|
|
70
|
+
(0, file_parameter_warnings_1.warnDeprecatedFileFields)(process.slug, (0, file_parameter_warnings_1.deprecatedFileFields)(process.script?.parametersSchema));
|
|
71
|
+
const updatedProcess = await this._fcodeGraphQLClient.pushProcess(process);
|
|
68
72
|
return this._repository.save(updatedProcess);
|
|
69
73
|
}
|
|
70
74
|
if (this._repository._repositoryName === 'modules') {
|
|
@@ -96,6 +96,7 @@ class I18n:
|
|
|
96
96
|
payload = i18n_payload or {}
|
|
97
97
|
self.__translations = payload.get("translations") or {}
|
|
98
98
|
self.__by_locale = payload.get("byLocale") or {}
|
|
99
|
+
self.__primary_locale = payload.get("primaryLocale")
|
|
99
100
|
self.locale = payload.get("locale")
|
|
100
101
|
|
|
101
102
|
def __call__(self, key, args=None, options=None):
|
|
@@ -125,24 +126,43 @@ class I18n:
|
|
|
125
126
|
"""The maps a requested locale opens, most specific first.
|
|
126
127
|
|
|
127
128
|
An identifier no locale matches is shortened one segment at a time ("en-gb" reads "en"),
|
|
128
|
-
by name first and ignoring case only as a last resort
|
|
129
|
-
|
|
129
|
+
by name first and ignoring case only as a last resort, and a language that still matches
|
|
130
|
+
nothing is broadened to a regional locale of it ("es" reads "es-ES", the primary one if it
|
|
131
|
+
is among them, else the alphabetically first) -- the same chain the cloud executors
|
|
132
|
+
resolve, so a local run answers the same identifiers the same way.
|
|
130
133
|
"""
|
|
131
134
|
if locale is None or not self.__by_locale:
|
|
132
135
|
return []
|
|
136
|
+
import re
|
|
137
|
+
|
|
133
138
|
names = list(self.__by_locale.keys())
|
|
134
139
|
chain = []
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
|
|
141
|
+
def matching(candidate):
|
|
137
142
|
if candidate in names:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
return candidate
|
|
144
|
+
folded = sorted(n for n in names if n.lower() == candidate.lower())
|
|
145
|
+
return folded[0] if folded else None
|
|
146
|
+
|
|
147
|
+
def add(match):
|
|
142
148
|
if match is not None and match not in chain:
|
|
143
149
|
chain.append(match)
|
|
150
|
+
|
|
151
|
+
candidate = locale
|
|
152
|
+
language = None
|
|
153
|
+
while candidate:
|
|
154
|
+
add(matching(candidate))
|
|
155
|
+
language = candidate
|
|
144
156
|
last_separator = max(candidate.rfind("-"), candidate.rfind("_"))
|
|
145
157
|
candidate = candidate[:last_separator] if last_separator > 0 else None
|
|
158
|
+
if language and not re.search(r"[-_]", language) and matching(language) is None:
|
|
159
|
+
same_language = sorted(
|
|
160
|
+
n for n in names if re.split(r"[-_]", n)[0].lower() == language.lower()
|
|
161
|
+
)
|
|
162
|
+
if self.__primary_locale in same_language:
|
|
163
|
+
add(self.__primary_locale)
|
|
164
|
+
elif same_language:
|
|
165
|
+
add(same_language[0])
|
|
146
166
|
return [self.__by_locale[name] for name in chain if self.__by_locale[name]]
|
|
147
167
|
|
|
148
168
|
|
|
@@ -4,6 +4,11 @@ const graphql_request_1 = require("graphql-request");
|
|
|
4
4
|
const types_1 = require("../core/types");
|
|
5
5
|
const locale_merger_1 = require("./locale-merger");
|
|
6
6
|
const _ = require("lodash");
|
|
7
|
+
// Source code travels base64 encoded: Cloudflare's WAF blocks requests whose body
|
|
8
|
+
// looks like an attack payload, which source code often does. sourceCodeEncoding tells the
|
|
9
|
+
// API to decode it back.
|
|
10
|
+
const SOURCE_CODE_ENCODING = 'BASE64';
|
|
11
|
+
const encodeSourceCode = (sourceCode) => sourceCode == null ? sourceCode : Buffer.from(sourceCode, 'utf8').toString('base64');
|
|
7
12
|
// Manifest fields an fcode-api older than this CLI may not know. The manifest query falls back to a
|
|
8
13
|
// narrower selection when the error names one of them, so a CLI stays usable against an
|
|
9
14
|
// on-premises api that has not been updated yet.
|
|
@@ -261,8 +266,19 @@ class FcodeGraphQLClient {
|
|
|
261
266
|
createVersionedProcesses(versionedProcess) {
|
|
262
267
|
return this.client
|
|
263
268
|
.request((0, graphql_request_1.gql) `
|
|
264
|
-
mutation publishProcess(
|
|
265
|
-
|
|
269
|
+
mutation publishProcess(
|
|
270
|
+
$processId: ID!
|
|
271
|
+
$tag: String!
|
|
272
|
+
$sourceCode: String
|
|
273
|
+
$sourceCodeEncoding: SourceCodeEncoding
|
|
274
|
+
$parametersSchema: JSON
|
|
275
|
+
$readme: String
|
|
276
|
+
) {
|
|
277
|
+
publishProcess(
|
|
278
|
+
processId: $processId
|
|
279
|
+
tag: $tag
|
|
280
|
+
input: { sourceCode: $sourceCode, sourceCodeEncoding: $sourceCodeEncoding, parametersSchema: $parametersSchema, readme: $readme }
|
|
281
|
+
) {
|
|
266
282
|
id
|
|
267
283
|
}
|
|
268
284
|
}
|
|
@@ -271,7 +287,8 @@ class FcodeGraphQLClient {
|
|
|
271
287
|
tag: versionedProcess.tag,
|
|
272
288
|
parametersSchema: versionedProcess.script.parametersSchema,
|
|
273
289
|
readme: versionedProcess.readme,
|
|
274
|
-
sourceCode: versionedProcess.script.sourceCode,
|
|
290
|
+
sourceCode: encodeSourceCode(versionedProcess.script.sourceCode),
|
|
291
|
+
sourceCodeEncoding: SOURCE_CODE_ENCODING,
|
|
275
292
|
})
|
|
276
293
|
.catch((error) => {
|
|
277
294
|
return Promise.reject(this.cleanError(error));
|
|
@@ -330,15 +347,20 @@ class FcodeGraphQLClient {
|
|
|
330
347
|
createVersionedScriptLibrary(versionedScriptLibrary) {
|
|
331
348
|
return this.client
|
|
332
349
|
.request((0, graphql_request_1.gql) `
|
|
333
|
-
mutation publishScriptLibrary($scriptLibraryId: ID!, $tag: String!, $sourceCode: String) {
|
|
334
|
-
publishScriptLibrary(
|
|
350
|
+
mutation publishScriptLibrary($scriptLibraryId: ID!, $tag: String!, $sourceCode: String, $sourceCodeEncoding: SourceCodeEncoding) {
|
|
351
|
+
publishScriptLibrary(
|
|
352
|
+
scriptLibraryId: $scriptLibraryId
|
|
353
|
+
tag: $tag
|
|
354
|
+
input: { sourceCode: $sourceCode, sourceCodeEncoding: $sourceCodeEncoding }
|
|
355
|
+
) {
|
|
335
356
|
id
|
|
336
357
|
}
|
|
337
358
|
}
|
|
338
359
|
`, {
|
|
339
360
|
scriptLibraryId: versionedScriptLibrary.id,
|
|
340
361
|
tag: versionedScriptLibrary.tag,
|
|
341
|
-
sourceCode: versionedScriptLibrary.sourceCode,
|
|
362
|
+
sourceCode: encodeSourceCode(versionedScriptLibrary.sourceCode),
|
|
363
|
+
sourceCodeEncoding: SOURCE_CODE_ENCODING,
|
|
342
364
|
})
|
|
343
365
|
.catch((error) => {
|
|
344
366
|
return Promise.reject(this.cleanError(error));
|
|
@@ -553,7 +575,8 @@ class FcodeGraphQLClient {
|
|
|
553
575
|
parametersSchema: process.script.parametersSchema,
|
|
554
576
|
readme: process.readme,
|
|
555
577
|
tags: process.tags,
|
|
556
|
-
sourceCode: process.script.sourceCode,
|
|
578
|
+
sourceCode: encodeSourceCode(process.script.sourceCode),
|
|
579
|
+
sourceCodeEncoding: SOURCE_CODE_ENCODING,
|
|
557
580
|
manifest: process.settings.dependencies.scopedToProcess ? process.manifest : null,
|
|
558
581
|
settings,
|
|
559
582
|
};
|
|
@@ -640,8 +663,8 @@ class FcodeGraphQLClient {
|
|
|
640
663
|
if (scriptLibrary.id) {
|
|
641
664
|
return this.client
|
|
642
665
|
.request((0, graphql_request_1.gql) `
|
|
643
|
-
mutation updateScriptLibrary($id: ID!, $sourceCode: String) {
|
|
644
|
-
updateScriptLibrary(id: $id, input: { sourceCode: $sourceCode }) {
|
|
666
|
+
mutation updateScriptLibrary($id: ID!, $sourceCode: String, $sourceCodeEncoding: SourceCodeEncoding) {
|
|
667
|
+
updateScriptLibrary(id: $id, input: { sourceCode: $sourceCode, sourceCodeEncoding: $sourceCodeEncoding }) {
|
|
645
668
|
id
|
|
646
669
|
name
|
|
647
670
|
programmingLanguage
|
|
@@ -650,7 +673,8 @@ class FcodeGraphQLClient {
|
|
|
650
673
|
}
|
|
651
674
|
`, {
|
|
652
675
|
id: scriptLibrary.id,
|
|
653
|
-
sourceCode: scriptLibrary.sourceCode,
|
|
676
|
+
sourceCode: encodeSourceCode(scriptLibrary.sourceCode),
|
|
677
|
+
sourceCodeEncoding: SOURCE_CODE_ENCODING,
|
|
654
678
|
})
|
|
655
679
|
.then(({ updateScriptLibrary: scriptLibrary }) => new types_1.ScriptLibrary(scriptLibrary.id, scriptLibrary.name, scriptLibrary.sourceCode, scriptLibrary.programmingLanguage))
|
|
656
680
|
.catch((error) => {
|
|
@@ -659,8 +683,18 @@ class FcodeGraphQLClient {
|
|
|
659
683
|
}
|
|
660
684
|
return this.client
|
|
661
685
|
.request((0, graphql_request_1.gql) `
|
|
662
|
-
mutation createScriptLibrary(
|
|
663
|
-
|
|
686
|
+
mutation createScriptLibrary(
|
|
687
|
+
$name: String!
|
|
688
|
+
$sourceCode: String
|
|
689
|
+
$sourceCodeEncoding: SourceCodeEncoding
|
|
690
|
+
$programmingLanguage: ProgrammingLanguage
|
|
691
|
+
) {
|
|
692
|
+
createScriptLibrary(
|
|
693
|
+
name: $name
|
|
694
|
+
programmingLanguage: $programmingLanguage
|
|
695
|
+
sourceCode: $sourceCode
|
|
696
|
+
sourceCodeEncoding: $sourceCodeEncoding
|
|
697
|
+
) {
|
|
664
698
|
id
|
|
665
699
|
name
|
|
666
700
|
sourceCode
|
|
@@ -669,7 +703,8 @@ class FcodeGraphQLClient {
|
|
|
669
703
|
}
|
|
670
704
|
`, {
|
|
671
705
|
name: scriptLibrary.name,
|
|
672
|
-
sourceCode: scriptLibrary.sourceCode,
|
|
706
|
+
sourceCode: encodeSourceCode(scriptLibrary.sourceCode),
|
|
707
|
+
sourceCodeEncoding: SOURCE_CODE_ENCODING,
|
|
673
708
|
programmingLanguage: scriptLibrary.programmingLanguage,
|
|
674
709
|
})
|
|
675
710
|
.then(({ createScriptLibrary: scriptLibrary }) => new types_1.ScriptLibrary(scriptLibrary.id, scriptLibrary.name, scriptLibrary.sourceCode, scriptLibrary.programmingLanguage))
|
|
@@ -16,7 +16,9 @@ export declare function flattenTranslations(document: unknown, prefix?: string,
|
|
|
16
16
|
* publishes plain languages, so the request degrades one segment at a time — split on `-` or `_`,
|
|
17
17
|
* without parsing a language tag, since any short slug is a legal identifier here — and every step
|
|
18
18
|
* that matches stays in the chain: a variant file translates the keys that differ and leaves the
|
|
19
|
-
* rest to the language it came from.
|
|
19
|
+
* rest to the language it came from. The opposite happens too — the host sends `es` while the
|
|
20
|
+
* workspace has `es-ES` — so a language layer that matches nothing, by name or ignoring case, is
|
|
21
|
+
* broadened to a regional file of that language, in the place the language would have taken.
|
|
20
22
|
*/
|
|
21
23
|
export declare function localeFallbackChain(requested: string | undefined, primary: string | undefined, available: Array<string>): Array<string>;
|
|
22
24
|
/**
|
|
@@ -25,10 +27,12 @@ export declare function localeFallbackChain(requested: string | undefined, prima
|
|
|
25
27
|
* shorter form of the requested one the workspace publishes — layered underneath, plus each
|
|
26
28
|
* locale's own map (inherited file layered under its own, but NOT merged over the primary) so a
|
|
27
29
|
* call naming a locale (`fcode.i18n(key, args, { locale })`) resolves like the cloud's `by_locale`:
|
|
28
|
-
* key by key in the requested locale first, falling back to the merged translations
|
|
30
|
+
* key by key in the requested locale first, falling back to the merged translations — and the
|
|
31
|
+
* primary locale, which a call naming a bare language prefers when it broadens to a regional file
|
|
29
32
|
*/
|
|
30
33
|
export declare function resolveLocalTranslations(requestedLocale?: string, workspaceDir?: string): {
|
|
31
34
|
locale?: string;
|
|
35
|
+
primaryLocale?: string;
|
|
32
36
|
translations: Record<string, string>;
|
|
33
37
|
byLocale: Record<string, Record<string, string>>;
|
|
34
38
|
};
|
|
@@ -120,6 +120,20 @@ function matchingLocale(candidate, available) {
|
|
|
120
120
|
}
|
|
121
121
|
return available.filter((locale) => locale.toLowerCase() === candidate.toLowerCase()).sort()[0];
|
|
122
122
|
}
|
|
123
|
+
const LOCALE_SEPARATOR = /[-_]/;
|
|
124
|
+
/**
|
|
125
|
+
* The available locale a bare language broadens to when the workspace only has regional files for
|
|
126
|
+
* it (`es` → `es-ES`): one whose first segment is the language, case ignored — the primary locale
|
|
127
|
+
* if it is one of them, the alphabetically first otherwise. A language with a separator in it (an
|
|
128
|
+
* identifier that starts with one) broadens to nothing. Mirrors the platform's `LocaleFallbackChain`.
|
|
129
|
+
*/
|
|
130
|
+
function sameLanguageLocale(language, primary, available) {
|
|
131
|
+
if (!language || LOCALE_SEPARATOR.test(language)) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const sameLanguage = available.filter((locale) => locale.split(LOCALE_SEPARATOR)[0].toLowerCase() === language.toLowerCase()).sort();
|
|
135
|
+
return primary !== undefined && sameLanguage.includes(primary) ? primary : sameLanguage[0];
|
|
136
|
+
}
|
|
123
137
|
/**
|
|
124
138
|
* The locale files that answer a requested identifier, lowest precedence first. Mirrors the
|
|
125
139
|
* platform's `LocaleFallbackChain`, so a local run and the cloud resolve the same text.
|
|
@@ -128,7 +142,9 @@ function matchingLocale(candidate, available) {
|
|
|
128
142
|
* publishes plain languages, so the request degrades one segment at a time — split on `-` or `_`,
|
|
129
143
|
* without parsing a language tag, since any short slug is a legal identifier here — and every step
|
|
130
144
|
* that matches stays in the chain: a variant file translates the keys that differ and leaves the
|
|
131
|
-
* rest to the language it came from.
|
|
145
|
+
* rest to the language it came from. The opposite happens too — the host sends `es` while the
|
|
146
|
+
* workspace has `es-ES` — so a language layer that matches nothing, by name or ignoring case, is
|
|
147
|
+
* broadened to a regional file of that language, in the place the language would have taken.
|
|
132
148
|
*/
|
|
133
149
|
function localeFallbackChain(requested, primary, available) {
|
|
134
150
|
const chain = [];
|
|
@@ -146,8 +162,14 @@ function localeFallbackChain(requested, primary, available) {
|
|
|
146
162
|
const lastSeparator = Math.max(candidate.lastIndexOf('-'), candidate.lastIndexOf('_'));
|
|
147
163
|
candidate = lastSeparator > 0 ? candidate.slice(0, lastSeparator) : undefined;
|
|
148
164
|
}
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
candidates.forEach((shorterOrExact, index) => {
|
|
166
|
+
if (index === 0 && !matchingLocale(shorterOrExact, available)) {
|
|
167
|
+
add(sameLanguageLocale(shorterOrExact, primary, available));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
add(shorterOrExact);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
151
173
|
return chain;
|
|
152
174
|
}
|
|
153
175
|
/**
|
|
@@ -156,14 +178,15 @@ function localeFallbackChain(requested, primary, available) {
|
|
|
156
178
|
* shorter form of the requested one the workspace publishes — layered underneath, plus each
|
|
157
179
|
* locale's own map (inherited file layered under its own, but NOT merged over the primary) so a
|
|
158
180
|
* call naming a locale (`fcode.i18n(key, args, { locale })`) resolves like the cloud's `by_locale`:
|
|
159
|
-
* key by key in the requested locale first, falling back to the merged translations
|
|
181
|
+
* key by key in the requested locale first, falling back to the merged translations — and the
|
|
182
|
+
* primary locale, which a call naming a bare language prefers when it broadens to a regional file
|
|
160
183
|
*/
|
|
161
184
|
function resolveLocalTranslations(requestedLocale, workspaceDir = process.cwd()) {
|
|
162
185
|
const primary = primaryLocaleOf(workspaceDir);
|
|
163
186
|
const locale = requestedLocale || primary;
|
|
164
187
|
const byLocale = Object.fromEntries(availableLocales(workspaceDir).map((slug) => [slug, readLocale(workspaceDir, slug)]));
|
|
165
188
|
if (!locale) {
|
|
166
|
-
return { locale: undefined, translations: {}, byLocale };
|
|
189
|
+
return { locale: undefined, primaryLocale: primary, translations: {}, byLocale };
|
|
167
190
|
}
|
|
168
191
|
// `byLocale` already holds every locale's own file, so the merged map — the fallback chain
|
|
169
192
|
// layered lowest precedence first — is derived from it rather than read from disk a second time.
|
|
@@ -171,5 +194,5 @@ function resolveLocalTranslations(requestedLocale, workspaceDir = process.cwd())
|
|
|
171
194
|
for (const layer of localeFallbackChain(requestedLocale, primary, Object.keys(byLocale))) {
|
|
172
195
|
Object.assign(translations, byLocale[layer]);
|
|
173
196
|
}
|
|
174
|
-
return { locale, translations, byLocale };
|
|
197
|
+
return { locale, primaryLocale: primary, translations, byLocale };
|
|
175
198
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface DeprecatedFileField {
|
|
2
|
+
/** Dotted path of the property inside the schema's `properties`, e.g. `dumpFile` or `upload.file`. */
|
|
3
|
+
path: string;
|
|
4
|
+
/** Extensions taken from `ui:options.accept`, the value `x-fcode-file.accept` should carry. */
|
|
5
|
+
accept: Array<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function deprecatedFileFields(schema: any): Array<DeprecatedFileField>;
|
|
8
|
+
export declare function warnDeprecatedFileFields(processSlug: string, fields: Array<DeprecatedFileField>): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deprecatedFileFields = deprecatedFileFields;
|
|
4
|
+
exports.warnDeprecatedFileFields = warnDeprecatedFileFields;
|
|
5
|
+
const color_1 = require("@oclif/color");
|
|
6
|
+
const cli_1 = require("./cli");
|
|
7
|
+
// A file parameter is declared with `x-fcode-file`. `"ui:widget": "file"` is an rjsf presentation
|
|
8
|
+
// key, so a property that only has the widget is still recognised as a file for now, but that
|
|
9
|
+
// fallback is deprecated and will go.
|
|
10
|
+
function deprecatedFileFields(schema) {
|
|
11
|
+
const fields = [];
|
|
12
|
+
collect(schema, [], fields);
|
|
13
|
+
return fields;
|
|
14
|
+
}
|
|
15
|
+
function collect(node, parents, fields) {
|
|
16
|
+
const properties = node?.properties;
|
|
17
|
+
if (!isObject(properties)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
21
|
+
if (!isObject(property)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const path = [...parents, key];
|
|
25
|
+
if (property.ui?.['ui:widget'] === 'file' && property['x-fcode-file'] == undefined) {
|
|
26
|
+
fields.push({ path: path.join('.'), accept: acceptList(property.ui['ui:options']?.accept) });
|
|
27
|
+
}
|
|
28
|
+
collect(property, path, fields);
|
|
29
|
+
collect(property.items, path, fields);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function acceptList(accept) {
|
|
33
|
+
const values = Array.isArray(accept) ? accept : typeof accept === 'string' ? accept.split(',') : [];
|
|
34
|
+
return values.map((value) => String(value).trim()).filter((value) => value !== '');
|
|
35
|
+
}
|
|
36
|
+
function isObject(value) {
|
|
37
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
// Naming extensions is the habit worth teaching: accept is what tells a Factorial One agent what the
|
|
40
|
+
// parameter takes. An empty list is still offered, so a field that really takes any file can say so.
|
|
41
|
+
function warnDeprecatedFileFields(processSlug, fields) {
|
|
42
|
+
for (const field of fields) {
|
|
43
|
+
const fix = field.accept.length > 0
|
|
44
|
+
? `Add "x-fcode-file": { "accept": ${extensionList(field.accept)} } to it in parametersSchema.json ` +
|
|
45
|
+
`(or { "accept": [] } if it really takes any file).`
|
|
46
|
+
: `Add "x-fcode-file" to it in parametersSchema.json, listing the extensions it takes, ` +
|
|
47
|
+
`e.g. { "accept": [".csv", ".xlsx"] }: they tell Factorial One what the parameter takes. ` +
|
|
48
|
+
`If it really takes any file, declare that with { "accept": [] }.`;
|
|
49
|
+
cli_1.default.log(color_1.default.yellow(`⚠️ '${processSlug}': parameter '${field.path}' is a file only through "ui:widget": "file", which is deprecated ` +
|
|
50
|
+
`for declaring a file and will stop working. ${fix}`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function extensionList(extensions) {
|
|
54
|
+
return `[${extensions.map((extension) => JSON.stringify(extension)).join(', ')}]`;
|
|
55
|
+
}
|
package/oclif.manifest.json
CHANGED