@factorialco/fcode-cli 3.5.0 → 3.6.0
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/commands/http.d.ts +2 -1
- package/dist/commands/http.js +4 -2
- package/dist/commands/settings/pull.js +5 -0
- package/dist/commands/settings/push.d.ts +6 -0
- package/dist/commands/settings/push.js +21 -26
- package/dist/config/workspace.d.ts +6 -0
- package/dist/config/workspace.js +9 -1
- package/dist/core/errors.d.ts +14 -0
- package/dist/core/errors.js +35 -1
- package/dist/core/executors/javascript-execution-task.js +21 -8
- package/dist/core/types.d.ts +40 -11
- package/dist/core/types.js +13 -1
- package/dist/repositories/settings.d.ts +22 -2
- package/dist/repositories/settings.js +129 -11
- package/dist/runtime/libs/python/fcode/context.py +28 -8
- package/dist/services/fcode-graphql-client.d.ts +8 -3
- package/dist/services/fcode-graphql-client.js +98 -48
- package/dist/services/locale-resolver.d.ts +6 -2
- package/dist/services/locale-resolver.js +35 -8
- package/dist/services/webhook-auth.d.ts +4 -2
- package/dist/services/webhook-auth.js +11 -5
- package/dist/utils/cli-version.d.ts +7 -0
- package/dist/utils/cli-version.js +20 -0
- package/dist/utils/gitignore.d.ts +2 -1
- package/dist/utils/gitignore.js +5 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -71,16 +71,20 @@ function readLocale(workspaceDir, locale) {
|
|
|
71
71
|
...readLocaleFile(workspaceDir, locale),
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
function
|
|
74
|
+
function primaryLocaleIn(workspaceDir, fileName) {
|
|
75
75
|
try {
|
|
76
76
|
(0, settings_1.migrateLegacySettingsFile)(workspaceDir);
|
|
77
|
-
const settings = fs.readJsonSync(path.join(workspaceDir,
|
|
77
|
+
const settings = fs.readJsonSync(path.join(workspaceDir, fileName));
|
|
78
78
|
return typeof settings?.primaryLocale === 'string' && settings.primaryLocale.length > 0 ? settings.primaryLocale : undefined;
|
|
79
79
|
}
|
|
80
80
|
catch {
|
|
81
81
|
return undefined;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
/** The workspace's own choice, or the one it inherits when settings.json makes none. */
|
|
85
|
+
function chosenPrimaryLocaleOf(workspaceDir) {
|
|
86
|
+
return primaryLocaleIn(workspaceDir, settings_1.SETTINGS_FILENAME) ?? primaryLocaleIn(workspaceDir, settings_1.INHERITED_SETTINGS_FILENAME);
|
|
87
|
+
}
|
|
84
88
|
/** Every locale file in the workspace, own and inherited: `pull` writes them all to `i18n/`. */
|
|
85
89
|
function localeFileSlugs(workspaceDir) {
|
|
86
90
|
const directory = path.join(workspaceDir, I18N_DIRECTORY);
|
|
@@ -120,6 +124,20 @@ function matchingLocale(candidate, available) {
|
|
|
120
124
|
}
|
|
121
125
|
return available.filter((locale) => locale.toLowerCase() === candidate.toLowerCase()).sort()[0];
|
|
122
126
|
}
|
|
127
|
+
const LOCALE_SEPARATOR = /[-_]/;
|
|
128
|
+
/**
|
|
129
|
+
* The available locale a bare language broadens to when the workspace only has regional files for
|
|
130
|
+
* it (`es` → `es-ES`): one whose first segment is the language, case ignored — the primary locale
|
|
131
|
+
* if it is one of them, the alphabetically first otherwise. A language with a separator in it (an
|
|
132
|
+
* identifier that starts with one) broadens to nothing. Mirrors the platform's `LocaleFallbackChain`.
|
|
133
|
+
*/
|
|
134
|
+
function sameLanguageLocale(language, primary, available) {
|
|
135
|
+
if (!language || LOCALE_SEPARATOR.test(language)) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const sameLanguage = available.filter((locale) => locale.split(LOCALE_SEPARATOR)[0].toLowerCase() === language.toLowerCase()).sort();
|
|
139
|
+
return primary !== undefined && sameLanguage.includes(primary) ? primary : sameLanguage[0];
|
|
140
|
+
}
|
|
123
141
|
/**
|
|
124
142
|
* The locale files that answer a requested identifier, lowest precedence first. Mirrors the
|
|
125
143
|
* platform's `LocaleFallbackChain`, so a local run and the cloud resolve the same text.
|
|
@@ -128,7 +146,9 @@ function matchingLocale(candidate, available) {
|
|
|
128
146
|
* publishes plain languages, so the request degrades one segment at a time — split on `-` or `_`,
|
|
129
147
|
* without parsing a language tag, since any short slug is a legal identifier here — and every step
|
|
130
148
|
* 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.
|
|
149
|
+
* rest to the language it came from. The opposite happens too — the host sends `es` while the
|
|
150
|
+
* workspace has `es-ES` — so a language layer that matches nothing, by name or ignoring case, is
|
|
151
|
+
* broadened to a regional file of that language, in the place the language would have taken.
|
|
132
152
|
*/
|
|
133
153
|
function localeFallbackChain(requested, primary, available) {
|
|
134
154
|
const chain = [];
|
|
@@ -146,8 +166,14 @@ function localeFallbackChain(requested, primary, available) {
|
|
|
146
166
|
const lastSeparator = Math.max(candidate.lastIndexOf('-'), candidate.lastIndexOf('_'));
|
|
147
167
|
candidate = lastSeparator > 0 ? candidate.slice(0, lastSeparator) : undefined;
|
|
148
168
|
}
|
|
149
|
-
|
|
150
|
-
|
|
169
|
+
candidates.forEach((shorterOrExact, index) => {
|
|
170
|
+
if (index === 0 && !matchingLocale(shorterOrExact, available)) {
|
|
171
|
+
add(sameLanguageLocale(shorterOrExact, primary, available));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
add(shorterOrExact);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
151
177
|
return chain;
|
|
152
178
|
}
|
|
153
179
|
/**
|
|
@@ -156,14 +182,15 @@ function localeFallbackChain(requested, primary, available) {
|
|
|
156
182
|
* shorter form of the requested one the workspace publishes — layered underneath, plus each
|
|
157
183
|
* locale's own map (inherited file layered under its own, but NOT merged over the primary) so a
|
|
158
184
|
* 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
|
|
185
|
+
* key by key in the requested locale first, falling back to the merged translations — and the
|
|
186
|
+
* primary locale, which a call naming a bare language prefers when it broadens to a regional file
|
|
160
187
|
*/
|
|
161
188
|
function resolveLocalTranslations(requestedLocale, workspaceDir = process.cwd()) {
|
|
162
189
|
const primary = primaryLocaleOf(workspaceDir);
|
|
163
190
|
const locale = requestedLocale || primary;
|
|
164
191
|
const byLocale = Object.fromEntries(availableLocales(workspaceDir).map((slug) => [slug, readLocale(workspaceDir, slug)]));
|
|
165
192
|
if (!locale) {
|
|
166
|
-
return { locale: undefined, translations: {}, byLocale };
|
|
193
|
+
return { locale: undefined, primaryLocale: primary, translations: {}, byLocale };
|
|
167
194
|
}
|
|
168
195
|
// `byLocale` already holds every locale's own file, so the merged map — the fallback chain
|
|
169
196
|
// layered lowest precedence first — is derived from it rather than read from disk a second time.
|
|
@@ -171,5 +198,5 @@ function resolveLocalTranslations(requestedLocale, workspaceDir = process.cwd())
|
|
|
171
198
|
for (const layer of localeFallbackChain(requestedLocale, primary, Object.keys(byLocale))) {
|
|
172
199
|
Object.assign(translations, byLocale[layer]);
|
|
173
200
|
}
|
|
174
|
-
return { locale, translations, byLocale };
|
|
201
|
+
return { locale, primaryLocale: primary, translations, byLocale };
|
|
175
202
|
}
|
|
@@ -13,9 +13,11 @@ export type WebhookAuthResult = {
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function findHeader(headers: http.IncomingHttpHeaders, name: string): string | undefined;
|
|
15
15
|
/**
|
|
16
|
-
* The configuration a webhook actually enforces: its own when CUSTOM, the workspace's when TEAM.
|
|
16
|
+
* The configuration a webhook actually enforces: its own when CUSTOM, the workspace's when TEAM. The
|
|
17
|
+
* workspace's is its own (settings.json) or, when settings.json leaves it out, the inherited one
|
|
18
|
+
* (settings.inherited.json); null in settings.json clears it, a parent's included.
|
|
17
19
|
* Returns null only when the webhook is public — TEAM without a workspace configuration throws,
|
|
18
20
|
* because treating it as public would silently unprotect a webhook its owner chose to protect.
|
|
19
21
|
*/
|
|
20
|
-
export declare function resolveEffectiveAuth(webhook: ProcessWebhook | undefined, teamWebhookAuth: WebhookAuth | undefined): WebhookAuth | null;
|
|
22
|
+
export declare function resolveEffectiveAuth(webhook: ProcessWebhook | undefined, teamWebhookAuth: WebhookAuth | null | undefined, inheritedTeamWebhookAuth?: WebhookAuth): WebhookAuth | null;
|
|
21
23
|
export declare function checkWebhookAuth(effectiveAuth: WebhookAuth | null, headers: http.IncomingHttpHeaders, variableValue: string | undefined): WebhookAuthResult;
|
|
@@ -26,20 +26,26 @@ function findHeader(headers, name) {
|
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* The configuration a webhook actually enforces: its own when CUSTOM, the workspace's when TEAM.
|
|
29
|
+
* The configuration a webhook actually enforces: its own when CUSTOM, the workspace's when TEAM. The
|
|
30
|
+
* workspace's is its own (settings.json) or, when settings.json leaves it out, the inherited one
|
|
31
|
+
* (settings.inherited.json); null in settings.json clears it, a parent's included.
|
|
30
32
|
* Returns null only when the webhook is public — TEAM without a workspace configuration throws,
|
|
31
33
|
* because treating it as public would silently unprotect a webhook its owner chose to protect.
|
|
32
34
|
*/
|
|
33
|
-
function resolveEffectiveAuth(webhook, teamWebhookAuth) {
|
|
35
|
+
function resolveEffectiveAuth(webhook, teamWebhookAuth, inheritedTeamWebhookAuth) {
|
|
34
36
|
const authMode = webhook?.authMode;
|
|
35
37
|
if (!authMode || authMode === 'NONE') {
|
|
36
38
|
return null;
|
|
37
39
|
}
|
|
38
40
|
if (authMode === 'TEAM') {
|
|
39
|
-
if (
|
|
40
|
-
throw new Error('this webhook inherits the workspace webhook auth, which
|
|
41
|
+
if (teamWebhookAuth === null) {
|
|
42
|
+
throw new Error('this webhook inherits the workspace webhook auth, which settings.json sets to none');
|
|
41
43
|
}
|
|
42
|
-
|
|
44
|
+
const workspaceAuth = teamWebhookAuth ?? inheritedTeamWebhookAuth;
|
|
45
|
+
if (!workspaceAuth) {
|
|
46
|
+
throw new Error('this webhook inherits the workspace webhook auth, which is not set in settings.json nor inherited');
|
|
47
|
+
}
|
|
48
|
+
return workspaceAuth;
|
|
43
49
|
}
|
|
44
50
|
if (!webhook?.auth?.variableKey) {
|
|
45
51
|
throw new Error('this webhook uses the CUSTOM auth mode but names no variable in metadata.json');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The header the CLI reports its version in, so the platform can refuse a CLI too old for a request. */
|
|
2
|
+
export declare const CLI_VERSION_HEADER = "Fcode-Cli-Version";
|
|
3
|
+
/**
|
|
4
|
+
* The installed CLI's own version, read from its package.json: two levels above this file both in
|
|
5
|
+
* src/utils (tests) and dist/utils (the published package, which always ships package.json).
|
|
6
|
+
*/
|
|
7
|
+
export declare function cliVersion(): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLI_VERSION_HEADER = void 0;
|
|
4
|
+
exports.cliVersion = cliVersion;
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
/** The header the CLI reports its version in, so the platform can refuse a CLI too old for a request. */
|
|
8
|
+
exports.CLI_VERSION_HEADER = 'Fcode-Cli-Version';
|
|
9
|
+
let cached;
|
|
10
|
+
/**
|
|
11
|
+
* The installed CLI's own version, read from its package.json: two levels above this file both in
|
|
12
|
+
* src/utils (tests) and dist/utils (the published package, which always ships package.json).
|
|
13
|
+
*/
|
|
14
|
+
function cliVersion() {
|
|
15
|
+
if (cached === undefined) {
|
|
16
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
17
|
+
cached = String(packageJson.version);
|
|
18
|
+
}
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
@@ -16,6 +16,7 @@ export declare function renderGitignoreWithInherited(existing: string, inherited
|
|
|
16
16
|
* current workspace metadata, so resources that stop being inherited drop out.
|
|
17
17
|
* Inherited variable values live in one file, which is ignored as soon as the
|
|
18
18
|
* workspace inherits any variable; inherited locales are one file each, like modules;
|
|
19
|
-
* inherited dependencies are one file per language, like variables
|
|
19
|
+
* inherited dependencies are one file per language, like variables; inherited settings are one
|
|
20
|
+
* file.
|
|
20
21
|
*/
|
|
21
22
|
export declare function syncInheritedGitignore(workspaceDir?: string): Promise<void>;
|
package/dist/utils/gitignore.js
CHANGED
|
@@ -9,6 +9,7 @@ const workspace_1 = require("../config/workspace");
|
|
|
9
9
|
const global_1 = require("../config/global");
|
|
10
10
|
const settings_1 = require("../config/settings");
|
|
11
11
|
const programming_language_manifest_1 = require("../repositories/programming-language-manifest");
|
|
12
|
+
const settings_2 = require("../repositories/settings");
|
|
12
13
|
const filenamify_1 = require("./filenamify");
|
|
13
14
|
const file_utils_1 = require("./file-utils");
|
|
14
15
|
exports.INHERITED_BLOCK_START = '# BEGIN fcode inherited resources (auto-generated, do not edit)';
|
|
@@ -51,7 +52,8 @@ function stripManagedBlock(content) {
|
|
|
51
52
|
* current workspace metadata, so resources that stop being inherited drop out.
|
|
52
53
|
* Inherited variable values live in one file, which is ignored as soon as the
|
|
53
54
|
* workspace inherits any variable; inherited locales are one file each, like modules;
|
|
54
|
-
* inherited dependencies are one file per language, like variables
|
|
55
|
+
* inherited dependencies are one file per language, like variables; inherited settings are one
|
|
56
|
+
* file.
|
|
55
57
|
*/
|
|
56
58
|
async function syncInheritedGitignore(workspaceDir = process.cwd()) {
|
|
57
59
|
const team = await new workspace_1.default(workspaceDir).getActiveTeam();
|
|
@@ -71,6 +73,8 @@ async function syncInheritedGitignore(workspaceDir = process.cwd()) {
|
|
|
71
73
|
// inherit — checked on disk because the manifest metadata tracks the workspace's own list, not
|
|
72
74
|
// what its parents contribute.
|
|
73
75
|
...exports.INHERITED_DEPENDENCY_FILES.filter((file) => fs.existsSync(path.join(workspaceDir, file))),
|
|
76
|
+
// Written only when the workspace inherits a setting, so checked on disk like dependencies.
|
|
77
|
+
...(fs.existsSync(path.join(workspaceDir, settings_2.INHERITED_SETTINGS_FILENAME)) ? [settings_2.INHERITED_SETTINGS_FILENAME] : []),
|
|
74
78
|
];
|
|
75
79
|
const gitignorePath = path.join(workspaceDir, '.gitignore');
|
|
76
80
|
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
package/oclif.manifest.json
CHANGED