@factorialco/fcode-cli 3.5.1 → 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/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/services/fcode-graphql-client.d.ts +8 -3
- package/dist/services/fcode-graphql-client.js +50 -35
- package/dist/services/locale-resolver.js +6 -2
- 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
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.6.0 linux-x64 node-v22.23.3
|
|
24
24
|
$ fcode --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ fcode COMMAND
|
package/dist/commands/http.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export default class Http extends Command {
|
|
|
14
14
|
* Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
|
|
15
15
|
* configuration names a variable, whose value is looked up in the workspace variables and compared
|
|
16
16
|
* against the configured header. A missing or malformed header, an undefined variable, a mismatch,
|
|
17
|
-
* or a webhook inheriting a workspace configuration that
|
|
17
|
+
* or a webhook inheriting a workspace configuration that is cleared or neither set nor inherited, is
|
|
18
|
+
* rejected.
|
|
18
19
|
* Returns true when the request may proceed, false after writing a 403.
|
|
19
20
|
*/
|
|
20
21
|
private _checkWebhookAuth;
|
package/dist/commands/http.js
CHANGED
|
@@ -122,7 +122,8 @@ class Http extends core_1.Command {
|
|
|
122
122
|
* Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
|
|
123
123
|
* configuration names a variable, whose value is looked up in the workspace variables and compared
|
|
124
124
|
* against the configured header. A missing or malformed header, an undefined variable, a mismatch,
|
|
125
|
-
* or a webhook inheriting a workspace configuration that
|
|
125
|
+
* or a webhook inheriting a workspace configuration that is cleared or neither set nor inherited, is
|
|
126
|
+
* rejected.
|
|
126
127
|
* Returns true when the request may proceed, false after writing a 403.
|
|
127
128
|
*/
|
|
128
129
|
async _checkWebhookAuth(req, res, processRepository, settingsRepository, slug) {
|
|
@@ -137,9 +138,10 @@ class Http extends core_1.Command {
|
|
|
137
138
|
// Read per request, like the process metadata above, so editing settings.json while the server
|
|
138
139
|
// runs takes effect without a restart.
|
|
139
140
|
const teamWebhookAuth = (await settingsRepository.find()).webhookAuth;
|
|
141
|
+
const inheritedTeamWebhookAuth = settingsRepository.findInherited().webhookAuth;
|
|
140
142
|
let effectiveAuth;
|
|
141
143
|
try {
|
|
142
|
-
effectiveAuth = (0, webhook_auth_1.resolveEffectiveAuth)(webhook, teamWebhookAuth);
|
|
144
|
+
effectiveAuth = (0, webhook_auth_1.resolveEffectiveAuth)(webhook, teamWebhookAuth, inheritedTeamWebhookAuth);
|
|
143
145
|
}
|
|
144
146
|
catch (error) {
|
|
145
147
|
// Fails closed, and says why: otherwise this reads as a wrong token rather than a local
|
|
@@ -5,6 +5,7 @@ const api_client_command_1 = require("../../core/commands/api-client-command");
|
|
|
5
5
|
const settings_1 = require("../../repositories/settings");
|
|
6
6
|
const types_1 = require("../../core/types");
|
|
7
7
|
const cli_1 = require("../../utils/cli");
|
|
8
|
+
const gitignore_1 = require("../../utils/gitignore");
|
|
8
9
|
class SettingsPullCommand extends api_client_command_1.default {
|
|
9
10
|
constructor() {
|
|
10
11
|
super(...arguments);
|
|
@@ -32,6 +33,10 @@ class SettingsPullCommand extends api_client_command_1.default {
|
|
|
32
33
|
const settings = await this._settingsRepository.fromRaw(raw);
|
|
33
34
|
this._settingsRepository.applyCloudVersioning(settings, versions, aliases);
|
|
34
35
|
this._settingsRepository.save(settings);
|
|
36
|
+
this._settingsRepository.saveInherited(await this._settingsRepository.inheritedFromRaw(raw));
|
|
37
|
+
// A read-only copy of the parents' values: keep it out of git even when this command runs on
|
|
38
|
+
// its own rather than inside `fcode pull`.
|
|
39
|
+
await (0, gitignore_1.syncInheritedGitignore)();
|
|
35
40
|
await this.workspaceConfig.setSettingsStoredHash(settings.contentHash);
|
|
36
41
|
}
|
|
37
42
|
}
|
|
@@ -8,6 +8,12 @@ export default class SettingsPushCommand extends ApiClientCommand {
|
|
|
8
8
|
};
|
|
9
9
|
_settingsRepository: SettingsRepository;
|
|
10
10
|
run(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* A settings.json some CLI synced (there is a stored hash) without marking it inheritance-aware was
|
|
13
|
+
* written by a CLI older than settings inheritance, with every effective value in it. Only a
|
|
14
|
+
* workspace with parents is affected: without any, its effective settings are its own.
|
|
15
|
+
*/
|
|
16
|
+
private pulledByAPreInheritanceCli;
|
|
11
17
|
/** Makes the cloud's versions/aliases match settings.json; returns failed manifest entries. */
|
|
12
18
|
private syncVersioning;
|
|
13
19
|
}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const color_1 = require("@oclif/color");
|
|
4
4
|
const api_client_command_1 = require("../../core/commands/api-client-command");
|
|
5
|
+
const errors_1 = require("../../core/errors");
|
|
5
6
|
const settings_1 = require("../../repositories/settings");
|
|
6
7
|
const types_1 = require("../../core/types");
|
|
7
8
|
const team_versioning_1 = require("../../services/team-versioning");
|
|
8
9
|
const cli_1 = require("../../utils/cli");
|
|
10
|
+
const gitignore_1 = require("../../utils/gitignore");
|
|
9
11
|
class SettingsPushCommand extends api_client_command_1.default {
|
|
10
12
|
constructor() {
|
|
11
13
|
super(...arguments);
|
|
@@ -23,30 +25,19 @@ class SettingsPushCommand extends api_client_command_1.default {
|
|
|
23
25
|
cli_1.default.log('Workspace settings already up to date');
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
update.zoneId = settings.zoneId;
|
|
28
|
+
if (await this.pulledByAPreInheritanceCli(settings, storedHash)) {
|
|
29
|
+
throw new errors_1.PreInheritanceSettingsFile();
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
update.webhookAuth = settings.webhookAuth
|
|
37
|
-
? { headerName: settings.webhookAuth.headerName ?? null, variableKey: settings.webhookAuth.variableKey }
|
|
38
|
-
: { variableKey: null };
|
|
39
|
-
// Sent unconditionally for the same reason as webhookAuth: settings.json is the source of truth,
|
|
40
|
-
// so removing primaryLocale from it must clear the cloud setting. Empty string is the clear.
|
|
41
|
-
update.primaryLocale = settings.primaryLocale ?? '';
|
|
42
|
-
await this._fcodeGraphQLClient.pushTeam(update);
|
|
31
|
+
const pushed = await this._fcodeGraphQLClient.pushTeam(await this._settingsRepository.toTeamUpdate(settings));
|
|
32
|
+
// What the workspace now inherits follows from what was just pushed.
|
|
33
|
+
this._settingsRepository.saveInherited(await this._settingsRepository.inheritedFromRaw(pushed));
|
|
34
|
+
// A read-only copy of the parents' values: keep it out of git even when this command runs on
|
|
35
|
+
// its own rather than inside `fcode pull`.
|
|
36
|
+
await (0, gitignore_1.syncInheritedGitignore)();
|
|
43
37
|
const failedEntities = await this.syncVersioning(settings, force);
|
|
44
38
|
// Re-read the cloud state so server-assigned fields (createdAt) and declined deletions
|
|
45
39
|
// land back in settings.json, then hash exactly what was written.
|
|
46
|
-
const [versions, aliases] = await Promise.all([
|
|
47
|
-
this._fcodeGraphQLClient.allTeamVersions(),
|
|
48
|
-
this._fcodeGraphQLClient.allTeamVersionAliases(),
|
|
49
|
-
]);
|
|
40
|
+
const [versions, aliases] = await Promise.all([this._fcodeGraphQLClient.allTeamVersions(), this._fcodeGraphQLClient.allTeamVersionAliases()]);
|
|
50
41
|
this._settingsRepository.applyCloudVersioning(settings, versions, aliases);
|
|
51
42
|
this._settingsRepository.save(settings);
|
|
52
43
|
await this.workspaceConfig.setSettingsStoredHash(settings.contentHash);
|
|
@@ -57,12 +48,17 @@ class SettingsPushCommand extends api_client_command_1.default {
|
|
|
57
48
|
this.error(`workspace versions pushed with ${failedEntities} failed entities`, { exit: 1 });
|
|
58
49
|
}
|
|
59
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* A settings.json some CLI synced (there is a stored hash) without marking it inheritance-aware was
|
|
53
|
+
* written by a CLI older than settings inheritance, with every effective value in it. Only a
|
|
54
|
+
* workspace with parents is affected: without any, its effective settings are its own.
|
|
55
|
+
*/
|
|
56
|
+
async pulledByAPreInheritanceCli(settings, storedHash) {
|
|
57
|
+
return storedHash !== undefined && settings.parentTeams.length > 0 && !(await this.workspaceConfig.isSettingsInheritanceAware());
|
|
58
|
+
}
|
|
60
59
|
/** Makes the cloud's versions/aliases match settings.json; returns failed manifest entries. */
|
|
61
60
|
async syncVersioning(settings, force) {
|
|
62
|
-
const [cloudVersions, cloudAliases] = await Promise.all([
|
|
63
|
-
this._fcodeGraphQLClient.allTeamVersions(),
|
|
64
|
-
this._fcodeGraphQLClient.allTeamVersionAliases(),
|
|
65
|
-
]);
|
|
61
|
+
const [cloudVersions, cloudAliases] = await Promise.all([this._fcodeGraphQLClient.allTeamVersions(), this._fcodeGraphQLClient.allTeamVersionAliases()]);
|
|
66
62
|
const plan = (0, team_versioning_1.planTeamVersioningSync)(settings, cloudVersions, cloudAliases);
|
|
67
63
|
if ((0, team_versioning_1.teamVersioningSyncPlanIsEmpty)(plan)) {
|
|
68
64
|
return 0;
|
|
@@ -96,8 +92,7 @@ class SettingsPushCommand extends api_client_command_1.default {
|
|
|
96
92
|
return failedEntities;
|
|
97
93
|
}
|
|
98
94
|
}
|
|
99
|
-
SettingsPushCommand.description = 'Update cloud workspace settings (timezone, error handler, inheritance, webhook auth) and sync ' +
|
|
100
|
-
'workspace versions/aliases from settings.json';
|
|
95
|
+
SettingsPushCommand.description = 'Update cloud workspace settings (timezone, error handler, inheritance, webhook auth) and sync ' + 'workspace versions/aliases from settings.json';
|
|
101
96
|
SettingsPushCommand.examples = ['$ fcode settings:push'];
|
|
102
97
|
SettingsPushCommand.flags = types_1.ForceFlag;
|
|
103
98
|
exports.default = SettingsPushCommand;
|
|
@@ -21,6 +21,12 @@ declare class WorkspaceConfig extends Config {
|
|
|
21
21
|
getStoredHash(resource: Resource): Promise<string>;
|
|
22
22
|
getSettingsStoredHash(): Promise<string | undefined>;
|
|
23
23
|
setSettingsStoredHash(contentHash: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Whether settings.json was last pulled or pushed by a CLI that knows settings inherit. One that
|
|
26
|
+
* did not wrote the values the workspace inherits as if they were its own, and records the hash
|
|
27
|
+
* without this mark.
|
|
28
|
+
*/
|
|
29
|
+
isSettingsInheritanceAware(): Promise<boolean>;
|
|
24
30
|
add(resourceType: string, resourceMetadata: ResourceMetadata): Promise<void>;
|
|
25
31
|
remove(resourceType: string, slug: string): Promise<void>;
|
|
26
32
|
}
|
package/dist/config/workspace.js
CHANGED
|
@@ -139,7 +139,15 @@ class WorkspaceConfig extends index_1.default {
|
|
|
139
139
|
}
|
|
140
140
|
async setSettingsStoredHash(contentHash) {
|
|
141
141
|
const team = await this.getActiveTeam();
|
|
142
|
-
await this.setTeam({ ...team, team: { contentHash } });
|
|
142
|
+
await this.setTeam({ ...team, team: { contentHash, inheritanceAware: true } });
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether settings.json was last pulled or pushed by a CLI that knows settings inherit. One that
|
|
146
|
+
* did not wrote the values the workspace inherits as if they were its own, and records the hash
|
|
147
|
+
* without this mark.
|
|
148
|
+
*/
|
|
149
|
+
async isSettingsInheritanceAware() {
|
|
150
|
+
return (await this.getActiveTeam()).team?.inheritanceAware === true;
|
|
143
151
|
}
|
|
144
152
|
async add(resourceType, resourceMetadata) {
|
|
145
153
|
const team = await this.getActiveTeam();
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -54,6 +54,13 @@ export declare class TeamAccessDenied extends CLIError {
|
|
|
54
54
|
export declare class NotFcodeWorkspace extends CLIError {
|
|
55
55
|
constructor();
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* settings.json was last synced by a CLI older than settings inheritance, which wrote the settings
|
|
59
|
+
* the workspace inherits as if they were its own: pushing it would stop the workspace inheriting them.
|
|
60
|
+
*/
|
|
61
|
+
export declare class PreInheritanceSettingsFile extends CLIError {
|
|
62
|
+
constructor();
|
|
63
|
+
}
|
|
57
64
|
export declare class DependenciesNotInstalled extends CLIError {
|
|
58
65
|
constructor(programmingLanguage: ProgrammingLanguage);
|
|
59
66
|
}
|
|
@@ -63,3 +70,10 @@ export declare class ProcessDependenciesNotInstalled extends CLIError {
|
|
|
63
70
|
export declare class ErrorExecutingProcess extends CLIError {
|
|
64
71
|
constructor(message: string);
|
|
65
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The platform answered without the team's inheritable settings: it predates settings inheritance,
|
|
75
|
+
* and reading its settings would take every one of them for inherited.
|
|
76
|
+
*/
|
|
77
|
+
export declare class PlatformPredatesSettingsInheritance extends CLIError {
|
|
78
|
+
constructor();
|
|
79
|
+
}
|
package/dist/core/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ErrorExecutingProcess = exports.ProcessDependenciesNotInstalled = exports.DependenciesNotInstalled = exports.NotFcodeWorkspace = exports.TeamAccessDenied = exports.NotAuthenticated = exports.CloudProgrammingLanguageManifestNotFound = exports.LocalProgrammingLanguageManifestNotFound = exports.CloudLocaleNotFound = exports.LocalLocaleNotFound = exports.CloudVariableNotFound = exports.LocalVariableNotFound = exports.CloudScriptLibraryNotFound = exports.LocalScriptLibraryNotFound = exports.CloudProcessNotFound = exports.LocalProcessNotFound = exports.ResourceNotFound = exports.CLIError = void 0;
|
|
3
|
+
exports.PlatformPredatesSettingsInheritance = exports.ErrorExecutingProcess = exports.ProcessDependenciesNotInstalled = exports.DependenciesNotInstalled = exports.PreInheritanceSettingsFile = exports.NotFcodeWorkspace = exports.TeamAccessDenied = exports.NotAuthenticated = exports.CloudProgrammingLanguageManifestNotFound = exports.LocalProgrammingLanguageManifestNotFound = exports.CloudLocaleNotFound = exports.LocalLocaleNotFound = exports.CloudVariableNotFound = exports.LocalVariableNotFound = exports.CloudScriptLibraryNotFound = exports.LocalScriptLibraryNotFound = exports.CloudProcessNotFound = exports.LocalProcessNotFound = exports.ResourceNotFound = exports.CLIError = void 0;
|
|
4
4
|
exports.teamAccessDeniedMessage = teamAccessDeniedMessage;
|
|
5
5
|
const global_1 = require("../config/global");
|
|
6
6
|
class CLIError extends Error {
|
|
@@ -145,6 +145,25 @@ class NotFcodeWorkspace extends CLIError {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
exports.NotFcodeWorkspace = NotFcodeWorkspace;
|
|
148
|
+
/**
|
|
149
|
+
* settings.json was last synced by a CLI older than settings inheritance, which wrote the settings
|
|
150
|
+
* the workspace inherits as if they were its own: pushing it would stop the workspace inheriting them.
|
|
151
|
+
*/
|
|
152
|
+
class PreInheritanceSettingsFile extends CLIError {
|
|
153
|
+
constructor() {
|
|
154
|
+
super('settings.json was last synced by an fcode CLI that predates settings inheritance: it holds the settings this ' +
|
|
155
|
+
'workspace inherits from its parent workspaces as if they were its own, and pushing it would stop the ' +
|
|
156
|
+
'workspace inheriting them.', {
|
|
157
|
+
code: 'ERR_PRE_INHERITANCE_SETTINGS_FILE',
|
|
158
|
+
exit: 1,
|
|
159
|
+
suggestions: [
|
|
160
|
+
'fcode settings:pull, then push again',
|
|
161
|
+
'fcode settings:pull --force if settings.json has local changes, then reapply them and push again',
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.PreInheritanceSettingsFile = PreInheritanceSettingsFile;
|
|
148
167
|
class DependenciesNotInstalled extends CLIError {
|
|
149
168
|
constructor(programmingLanguage) {
|
|
150
169
|
super(`Dependencies not installed.`, {
|
|
@@ -175,3 +194,18 @@ class ErrorExecutingProcess extends CLIError {
|
|
|
175
194
|
}
|
|
176
195
|
}
|
|
177
196
|
exports.ErrorExecutingProcess = ErrorExecutingProcess;
|
|
197
|
+
/**
|
|
198
|
+
* The platform answered without the team's inheritable settings: it predates settings inheritance,
|
|
199
|
+
* and reading its settings would take every one of them for inherited.
|
|
200
|
+
*/
|
|
201
|
+
class PlatformPredatesSettingsInheritance extends CLIError {
|
|
202
|
+
constructor() {
|
|
203
|
+
super('This Factorial Code platform predates settings inheritance, which this fcode CLI requires to read and ' +
|
|
204
|
+
'push workspace settings.', {
|
|
205
|
+
code: 'ERR_PLATFORM_PREDATES_SETTINGS_INHERITANCE',
|
|
206
|
+
exit: 1,
|
|
207
|
+
suggestions: ['Wait for the platform to be updated, or use an fcode CLI older than 3.6.0 until then'],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.PlatformPredatesSettingsInheritance = PlatformPredatesSettingsInheritance;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export interface TeamMetadata {
|
|
|
49
49
|
};
|
|
50
50
|
team?: {
|
|
51
51
|
contentHash?: string;
|
|
52
|
+
inheritanceAware?: boolean;
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
export interface RemoteMetadata {
|
|
@@ -397,24 +398,34 @@ export interface ParentTeamRef {
|
|
|
397
398
|
slug: string;
|
|
398
399
|
version?: string;
|
|
399
400
|
}
|
|
400
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* The workspace's settings.json: what `fcode settings:pull|push` syncs against the cloud team. It
|
|
403
|
+
* holds the team's own values only: an absent setting inherits, and null (error handler and webhook
|
|
404
|
+
* auth only) clears it, a parent's value included.
|
|
405
|
+
*/
|
|
401
406
|
export declare class WorkspaceSettings extends Resource {
|
|
402
407
|
id: string | undefined;
|
|
403
408
|
slug: string;
|
|
404
409
|
zoneId?: string;
|
|
405
|
-
errorHandlerConfig?: ErrorHandlerConfig;
|
|
410
|
+
errorHandlerConfig?: ErrorHandlerConfig | null;
|
|
406
411
|
parentTeams: ParentTeamRef[];
|
|
407
|
-
webhookAuth?: WebhookAuth;
|
|
412
|
+
webhookAuth?: WebhookAuth | null;
|
|
408
413
|
primaryLocale?: string;
|
|
409
414
|
versions?: TeamVersionFileEntry[];
|
|
410
415
|
aliases?: TeamAliasFileEntry[];
|
|
411
|
-
constructor(slug: string, parentTeams: ParentTeamRef[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth, primaryLocale?: string);
|
|
416
|
+
constructor(slug: string, parentTeams: ParentTeamRef[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig | null, webhookAuth?: WebhookAuth | null, primaryLocale?: string);
|
|
412
417
|
get contentHash(): string;
|
|
413
418
|
}
|
|
414
419
|
export interface RawErrorHandlerConfig {
|
|
415
420
|
processId: string;
|
|
416
421
|
tag?: string | null;
|
|
417
422
|
}
|
|
423
|
+
export type InheritableSetting = 'ERROR_HANDLER' | 'PARAMS_SCHEMA_VALIDATION' | 'PRIMARY_LOCALE' | 'WEBHOOK_AUTH' | 'ZONE_ID';
|
|
424
|
+
export type SettingMode = 'INHERIT' | 'NONE' | 'OWN';
|
|
425
|
+
export interface SettingOrigin {
|
|
426
|
+
mode: SettingMode;
|
|
427
|
+
from?: string | null;
|
|
428
|
+
}
|
|
418
429
|
export interface RawTeamSettings {
|
|
419
430
|
slug: string;
|
|
420
431
|
zoneId?: string;
|
|
@@ -422,16 +433,34 @@ export interface RawTeamSettings {
|
|
|
422
433
|
parentTeams: ParentTeamRef[];
|
|
423
434
|
webhookAuth?: WebhookAuth | null;
|
|
424
435
|
primaryLocale?: string | null;
|
|
436
|
+
origins?: Record<InheritableSetting, SettingOrigin>;
|
|
425
437
|
}
|
|
426
|
-
export interface
|
|
438
|
+
export interface InheritedWorkspaceSettings {
|
|
427
439
|
zoneId?: string;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
webhookAuth?: {
|
|
431
|
-
headerName?: string | null;
|
|
432
|
-
variableKey?: string | null;
|
|
433
|
-
} | null;
|
|
440
|
+
errorHandlerConfig?: ErrorHandlerConfig;
|
|
441
|
+
webhookAuth?: WebhookAuth;
|
|
434
442
|
primaryLocale?: string;
|
|
443
|
+
inheritedFrom: Record<string, string>;
|
|
444
|
+
}
|
|
445
|
+
export type SettingInput<T> = {
|
|
446
|
+
mode: 'INHERIT';
|
|
447
|
+
} | {
|
|
448
|
+
mode: 'NONE';
|
|
449
|
+
} | {
|
|
450
|
+
mode: 'OWN';
|
|
451
|
+
value: T;
|
|
452
|
+
};
|
|
453
|
+
export interface TeamUpdateInput {
|
|
454
|
+
parentTeams?: ParentTeamRef[];
|
|
455
|
+
inheritableSettings?: {
|
|
456
|
+
zoneId?: SettingInput<string>;
|
|
457
|
+
errorHandlerConfig?: SettingInput<RawErrorHandlerConfig>;
|
|
458
|
+
webhookAuth?: SettingInput<{
|
|
459
|
+
headerName?: string | null;
|
|
460
|
+
variableKey: string;
|
|
461
|
+
}>;
|
|
462
|
+
primaryLocale?: SettingInput<string>;
|
|
463
|
+
};
|
|
435
464
|
}
|
|
436
465
|
export type ResourceMetadata = {
|
|
437
466
|
[key: string]: any;
|
package/dist/core/types.js
CHANGED
|
@@ -261,7 +261,11 @@ class ProgrammingLanguageManifest extends Resource {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
exports.ProgrammingLanguageManifest = ProgrammingLanguageManifest;
|
|
264
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* The workspace's settings.json: what `fcode settings:pull|push` syncs against the cloud team. It
|
|
266
|
+
* holds the team's own values only: an absent setting inherits, and null (error handler and webhook
|
|
267
|
+
* auth only) clears it, a parent's value included.
|
|
268
|
+
*/
|
|
265
269
|
class WorkspaceSettings extends Resource {
|
|
266
270
|
constructor(slug, parentTeams, zoneId, errorHandlerConfig, webhookAuth, primaryLocale) {
|
|
267
271
|
super();
|
|
@@ -293,6 +297,14 @@ class WorkspaceSettings extends Resource {
|
|
|
293
297
|
if (this.primaryLocale) {
|
|
294
298
|
payload.primaryLocale = this.primaryLocale;
|
|
295
299
|
}
|
|
300
|
+
// Cleared settings hash apart from inherited ones; inserted only when any is cleared.
|
|
301
|
+
const cleared = [
|
|
302
|
+
...(this.errorHandlerConfig === null ? ['ERROR_HANDLER'] : []),
|
|
303
|
+
...(this.webhookAuth === null ? ['WEBHOOK_AUTH'] : []),
|
|
304
|
+
];
|
|
305
|
+
if (cleared.length > 0) {
|
|
306
|
+
payload.cleared = cleared;
|
|
307
|
+
}
|
|
296
308
|
// Versions hash only their tags and aliases their name->tag pairs: createdAt is
|
|
297
309
|
// server-assigned and a comment is immutable once published (there is no update mutation),
|
|
298
310
|
// so neither can represent a pushable local change. Inserted only when non-empty, again to
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { RawTeamSettings, TeamVersion, TeamVersionAlias, WorkspaceSettings } from '../core/types';
|
|
1
|
+
import { InheritedWorkspaceSettings, RawTeamSettings, TeamUpdateInput, TeamVersion, TeamVersionAlias, WorkspaceSettings } from '../core/types';
|
|
2
2
|
import WorkspaceConfig from '../config/workspace';
|
|
3
3
|
export declare const SETTINGS_FILENAME = "settings.json";
|
|
4
|
+
export declare const INHERITED_SETTINGS_FILENAME = "settings.inherited.json";
|
|
4
5
|
/**
|
|
5
6
|
* Workspaces cloned before the file was renamed carry team.json. Renaming it on first access keeps
|
|
6
7
|
* the stored sync hash valid (it is computed over the content, not the name), so the workspace does
|
|
@@ -22,8 +23,23 @@ export default class SettingsRepository {
|
|
|
22
23
|
exists(): boolean;
|
|
23
24
|
find(): Promise<WorkspaceSettings>;
|
|
24
25
|
save(settings: WorkspaceSettings): void;
|
|
25
|
-
/**
|
|
26
|
+
/** The settings a local run inherits; empty when the workspace inherits none. */
|
|
27
|
+
findInherited(): InheritedWorkspaceSettings;
|
|
28
|
+
/** Writes settings.inherited.json, or removes it when the workspace inherits no setting. */
|
|
29
|
+
saveInherited(inherited: InheritedWorkspaceSettings | undefined): void;
|
|
30
|
+
/**
|
|
31
|
+
* Builds the settings.json view of the cloud's raw payload: only the team's own values, null for a
|
|
32
|
+
* cleared setting, and nothing for an inherited or default one. Resolves the error-handler id to a
|
|
33
|
+
* slug.
|
|
34
|
+
*/
|
|
26
35
|
fromRaw(raw: RawTeamSettings): Promise<WorkspaceSettings>;
|
|
36
|
+
/** The settings.inherited.json view of the cloud's raw payload; undefined when nothing is inherited. */
|
|
37
|
+
inheritedFromRaw(raw: RawTeamSettings): Promise<InheritedWorkspaceSettings | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* settings.json is the source of truth for the team's own values: a setting absent from it is
|
|
40
|
+
* reset to inherit, null leaves the team with none (a parent's included), and a value sets it.
|
|
41
|
+
*/
|
|
42
|
+
toTeamUpdate(settings: WorkspaceSettings): Promise<TeamUpdateInput>;
|
|
27
43
|
/**
|
|
28
44
|
* Writes the cloud's workspace versions and aliases into the settings as the settings.json
|
|
29
45
|
* versioning fields (sorted for stable git diffs). Cloud entries always carry a createdAt; only
|
|
@@ -32,6 +48,10 @@ export default class SettingsRepository {
|
|
|
32
48
|
applyCloudVersioning(settings: WorkspaceSettings, versions: TeamVersion[], aliases: TeamVersionAlias[]): void;
|
|
33
49
|
resolveSlugToId(processSlug: string): Promise<string>;
|
|
34
50
|
resolveIdToSlug(processId: string): Promise<string>;
|
|
51
|
+
/** Where each setting comes from; an API without it predates settings inheritance. */
|
|
52
|
+
private originsOf;
|
|
53
|
+
private resolveErrorHandler;
|
|
54
|
+
private parseString;
|
|
35
55
|
private parseErrorHandler;
|
|
36
56
|
/**
|
|
37
57
|
* settings.json is hand-edited to declare versions, so entries without a tag are dropped and a
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SETTINGS_FILENAME = void 0;
|
|
3
|
+
exports.INHERITED_SETTINGS_FILENAME = exports.SETTINGS_FILENAME = void 0;
|
|
4
4
|
exports.migrateLegacySettingsFile = migrateLegacySettingsFile;
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const types_1 = require("../core/types");
|
|
8
8
|
const workspace_1 = require("../config/workspace");
|
|
9
|
+
const errors_1 = require("../core/errors");
|
|
9
10
|
const file_utils_1 = require("../utils/file-utils");
|
|
10
11
|
exports.SETTINGS_FILENAME = 'settings.json';
|
|
12
|
+
exports.INHERITED_SETTINGS_FILENAME = 'settings.inherited.json';
|
|
13
|
+
/** The inheritable settings settings.json manages, by their field name there. */
|
|
14
|
+
const SETTING_FIELDS = {
|
|
15
|
+
ZONE_ID: 'zoneId',
|
|
16
|
+
ERROR_HANDLER: 'errorHandlerConfig',
|
|
17
|
+
WEBHOOK_AUTH: 'webhookAuth',
|
|
18
|
+
PRIMARY_LOCALE: 'primaryLocale',
|
|
19
|
+
};
|
|
11
20
|
const LEGACY_SETTINGS_FILENAME = 'team.json';
|
|
12
21
|
/**
|
|
13
22
|
* Workspaces cloned before the file was renamed carry team.json. Renaming it on first access keeps
|
|
@@ -22,6 +31,14 @@ function migrateLegacySettingsFile(workspaceDir = process.cwd()) {
|
|
|
22
31
|
fs.renameSync(legacy, current);
|
|
23
32
|
}
|
|
24
33
|
}
|
|
34
|
+
/** A setting settings.json holds a value of or leaves out: its own value, or inherit. */
|
|
35
|
+
function ownOrInherit(value) {
|
|
36
|
+
return value === undefined ? { mode: 'INHERIT' } : { mode: 'OWN', value };
|
|
37
|
+
}
|
|
38
|
+
/** A setting settings.json leaves out (inherit) or sets to null (none). */
|
|
39
|
+
function noneOrInherit(value) {
|
|
40
|
+
return value === null ? { mode: 'NONE' } : { mode: 'INHERIT' };
|
|
41
|
+
}
|
|
25
42
|
/**
|
|
26
43
|
* Reads/writes the singleton settings.json (timezone, error handler, inheritance, webhook auth) at
|
|
27
44
|
* the workspace root. Unlike collection resources this is not a ResourceRepositoryBase:
|
|
@@ -47,7 +64,7 @@ class SettingsRepository {
|
|
|
47
64
|
raw = fs.readJsonSync(exports.SETTINGS_FILENAME) || {};
|
|
48
65
|
}
|
|
49
66
|
catch { }
|
|
50
|
-
const settings = new types_1.WorkspaceSettings(slug, this.parseParentTeams(raw.parentTeams), raw.zoneId,
|
|
67
|
+
const settings = new types_1.WorkspaceSettings(slug, this.parseParentTeams(raw.parentTeams), this.parseString(raw.zoneId), raw.errorHandlerConfig === null ? null : this.parseErrorHandler(raw.errorHandlerConfig), raw.webhookAuth === null ? null : this.parseWebhookAuth(raw.webhookAuth), this.parseString(raw.primaryLocale));
|
|
51
68
|
if (Array.isArray(raw.versions)) {
|
|
52
69
|
settings.versions = this.parseVersions(raw.versions);
|
|
53
70
|
}
|
|
@@ -60,16 +77,97 @@ class SettingsRepository {
|
|
|
60
77
|
migrateLegacySettingsFile();
|
|
61
78
|
(0, file_utils_1.outputFileSyncIfChanged)(exports.SETTINGS_FILENAME, JSON.stringify(this.toFile(settings), null, 2));
|
|
62
79
|
}
|
|
63
|
-
/**
|
|
80
|
+
/** The settings a local run inherits; empty when the workspace inherits none. */
|
|
81
|
+
findInherited() {
|
|
82
|
+
let raw = {};
|
|
83
|
+
try {
|
|
84
|
+
raw = fs.readJsonSync(exports.INHERITED_SETTINGS_FILENAME) || {};
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
return {
|
|
88
|
+
zoneId: this.parseString(raw.zoneId),
|
|
89
|
+
errorHandlerConfig: this.parseErrorHandler(raw.errorHandlerConfig),
|
|
90
|
+
webhookAuth: this.parseWebhookAuth(raw.webhookAuth),
|
|
91
|
+
primaryLocale: this.parseString(raw.primaryLocale),
|
|
92
|
+
inheritedFrom: raw.inheritedFrom ?? {},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Writes settings.inherited.json, or removes it when the workspace inherits no setting. */
|
|
96
|
+
saveInherited(inherited) {
|
|
97
|
+
if (!inherited) {
|
|
98
|
+
if (fs.pathExistsSync(exports.INHERITED_SETTINGS_FILENAME)) {
|
|
99
|
+
// Written read-only, so it has to be made writable before it can go.
|
|
100
|
+
fs.chmodSync(exports.INHERITED_SETTINGS_FILENAME, 0o644);
|
|
101
|
+
fs.removeSync(exports.INHERITED_SETTINGS_FILENAME);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Read-only like the other inherited files: the values belong to a parent workspace.
|
|
106
|
+
(0, file_utils_1.outputFileSyncIfChanged)(exports.INHERITED_SETTINGS_FILENAME, JSON.stringify(inherited, null, 2), { readOnly: true });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Builds the settings.json view of the cloud's raw payload: only the team's own values, null for a
|
|
110
|
+
* cleared setting, and nothing for an inherited or default one. Resolves the error-handler id to a
|
|
111
|
+
* slug.
|
|
112
|
+
*/
|
|
64
113
|
async fromRaw(raw) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
114
|
+
const origins = this.originsOf(raw);
|
|
115
|
+
const own = (setting, value) => {
|
|
116
|
+
const { mode } = origins[setting];
|
|
117
|
+
if (mode === 'NONE') {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return mode === 'OWN' ? value() : undefined;
|
|
121
|
+
};
|
|
122
|
+
return new types_1.WorkspaceSettings(raw.slug, raw.parentTeams ?? [], own('ZONE_ID', () => this.parseString(raw.zoneId)) ?? undefined, await own('ERROR_HANDLER', () => this.resolveErrorHandler(raw)), own('WEBHOOK_AUTH', () => this.parseWebhookAuth(raw.webhookAuth)), own('PRIMARY_LOCALE', () => this.parseString(raw.primaryLocale)) ?? undefined);
|
|
123
|
+
}
|
|
124
|
+
/** The settings.inherited.json view of the cloud's raw payload; undefined when nothing is inherited. */
|
|
125
|
+
async inheritedFromRaw(raw) {
|
|
126
|
+
const origins = this.originsOf(raw);
|
|
127
|
+
const inheritedFrom = {};
|
|
128
|
+
for (const [setting, field] of Object.entries(SETTING_FIELDS)) {
|
|
129
|
+
const { mode, from } = origins[setting];
|
|
130
|
+
if (mode === 'INHERIT' && from) {
|
|
131
|
+
inheritedFrom[field] = from;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (Object.keys(inheritedFrom).length === 0) {
|
|
135
|
+
return undefined;
|
|
71
136
|
}
|
|
72
|
-
|
|
137
|
+
// JSON.stringify drops the settings left undefined, so the file lists only inherited ones.
|
|
138
|
+
return {
|
|
139
|
+
zoneId: inheritedFrom.zoneId ? this.parseString(raw.zoneId) : undefined,
|
|
140
|
+
errorHandlerConfig: inheritedFrom.errorHandlerConfig ? await this.resolveErrorHandler(raw) : undefined,
|
|
141
|
+
webhookAuth: inheritedFrom.webhookAuth ? this.parseWebhookAuth(raw.webhookAuth) : undefined,
|
|
142
|
+
primaryLocale: inheritedFrom.primaryLocale ? this.parseString(raw.primaryLocale) : undefined,
|
|
143
|
+
inheritedFrom,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* settings.json is the source of truth for the team's own values: a setting absent from it is
|
|
148
|
+
* reset to inherit, null leaves the team with none (a parent's included), and a value sets it.
|
|
149
|
+
*/
|
|
150
|
+
async toTeamUpdate(settings) {
|
|
151
|
+
const { errorHandlerConfig, webhookAuth } = settings;
|
|
152
|
+
return {
|
|
153
|
+
parentTeams: settings.parentTeams,
|
|
154
|
+
inheritableSettings: {
|
|
155
|
+
zoneId: ownOrInherit(settings.zoneId),
|
|
156
|
+
errorHandlerConfig: errorHandlerConfig === undefined || errorHandlerConfig === null
|
|
157
|
+
? noneOrInherit(errorHandlerConfig)
|
|
158
|
+
: {
|
|
159
|
+
mode: 'OWN',
|
|
160
|
+
value: {
|
|
161
|
+
processId: await this.resolveSlugToId(errorHandlerConfig.processSlug),
|
|
162
|
+
tag: errorHandlerConfig.tag ?? null,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
webhookAuth: webhookAuth === undefined || webhookAuth === null
|
|
166
|
+
? noneOrInherit(webhookAuth)
|
|
167
|
+
: { mode: 'OWN', value: { headerName: webhookAuth.headerName ?? null, variableKey: webhookAuth.variableKey } },
|
|
168
|
+
primaryLocale: ownOrInherit(settings.primaryLocale),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
73
171
|
}
|
|
74
172
|
/**
|
|
75
173
|
* Writes the cloud's workspace versions and aliases into the settings as the settings.json
|
|
@@ -99,6 +197,25 @@ class SettingsRepository {
|
|
|
99
197
|
const match = Object.values(processes).find((process) => process.id === processId);
|
|
100
198
|
return match?.slug ?? processId;
|
|
101
199
|
}
|
|
200
|
+
/** Where each setting comes from; an API without it predates settings inheritance. */
|
|
201
|
+
originsOf(raw) {
|
|
202
|
+
if (!raw.origins) {
|
|
203
|
+
throw new errors_1.PlatformPredatesSettingsInheritance();
|
|
204
|
+
}
|
|
205
|
+
return raw.origins;
|
|
206
|
+
}
|
|
207
|
+
async resolveErrorHandler(raw) {
|
|
208
|
+
if (!raw.errorHandlerConfig?.processId) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
processSlug: await this.resolveIdToSlug(raw.errorHandlerConfig.processId),
|
|
213
|
+
tag: raw.errorHandlerConfig.tag ?? null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
parseString(raw) {
|
|
217
|
+
return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
|
|
218
|
+
}
|
|
102
219
|
parseErrorHandler(raw) {
|
|
103
220
|
if (!raw || !raw.processSlug) {
|
|
104
221
|
return undefined;
|
|
@@ -194,7 +311,8 @@ class SettingsRepository {
|
|
|
194
311
|
if (settings.errorHandlerConfig !== undefined) {
|
|
195
312
|
file.errorHandlerConfig = settings.errorHandlerConfig;
|
|
196
313
|
}
|
|
197
|
-
// Last, so adding webhook auth to an existing settings.json is a purely additive diff.
|
|
314
|
+
// Last, so adding webhook auth to an existing settings.json is a purely additive diff. null
|
|
315
|
+
// (cleared) is written as is: absent means inherit.
|
|
198
316
|
if (settings.webhookAuth !== undefined) {
|
|
199
317
|
file.webhookAuth = settings.webhookAuth;
|
|
200
318
|
}
|
|
@@ -88,9 +88,9 @@ declare class FcodeGraphQLClient {
|
|
|
88
88
|
private mapFactorialSettings;
|
|
89
89
|
mapProcess({ id, slug, name, description, tags, script, readme, manifest, settings, triggers, resolvingTeamSlug, lifecycleRole, deletedAt, retainingVersionTags, }: any): Process;
|
|
90
90
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* undefined.
|
|
91
|
+
* The team's inheritable settings, each read from its `{ mode, value, from }`: the value in effect
|
|
92
|
+
* and where it comes from. An API that predates settings inheritance has no such field, and leaves
|
|
93
|
+
* `origins` undefined so the settings commands refuse instead of reading every setting as inherited.
|
|
94
94
|
*/
|
|
95
95
|
mapTeamSettings(team: any): RawTeamSettings;
|
|
96
96
|
mapTeamVersion(teamVersion: any): TeamVersion;
|
|
@@ -105,6 +105,11 @@ declare class FcodeGraphQLClient {
|
|
|
105
105
|
verifyTeamAccess(): Promise<void>;
|
|
106
106
|
team(): Promise<RawTeamSettings>;
|
|
107
107
|
pushTeam(update: TeamUpdateInput): Promise<RawTeamSettings>;
|
|
108
|
+
/**
|
|
109
|
+
* An API that predates settings inheritance rejects the query naming `inheritableSettings` as a
|
|
110
|
+
* schema error; that reads better as what it means than as GraphQL validation output.
|
|
111
|
+
*/
|
|
112
|
+
cleanTeamSettingsError(error: any): any;
|
|
108
113
|
mapProgrammingLanguageManifest(programmingLanguageManifest: any): ProgrammingLanguageManifest;
|
|
109
114
|
private mapDependencies;
|
|
110
115
|
/**
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
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
|
+
const cli_version_1 = require("../utils/cli-version");
|
|
7
|
+
const errors_1 = require("../core/errors");
|
|
6
8
|
const _ = require("lodash");
|
|
7
9
|
// Source code travels base64 encoded: Cloudflare's WAF blocks requests whose body
|
|
8
10
|
// looks like an attack payload, which source code often does. sourceCodeEncoding tells the
|
|
@@ -137,6 +139,17 @@ const PROCESS_FIELDS = `
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
`;
|
|
142
|
+
// Each inheritable setting of a team: its mode, the value in effect and the parent it comes from.
|
|
143
|
+
const INHERITABLE_SETTINGS_SELECTION = `
|
|
144
|
+
inheritableSettings {
|
|
145
|
+
zoneId { mode value from }
|
|
146
|
+
paramsSchemaValidationEnabled { mode value from }
|
|
147
|
+
errorHandlerConfig { mode value { processId tag } from }
|
|
148
|
+
webhookAuth { mode value { headerName variableKey } from }
|
|
149
|
+
primaryLocale { mode value from }
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
const UNKNOWN_FIELD_ERROR = /FieldUndefined|WrongType|is undefined|not present|not in type/;
|
|
140
153
|
const NETWORK_ERROR_CODES = ['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'ETIMEDOUT', 'EAI_AGAIN'];
|
|
141
154
|
class FcodeGraphQLClient {
|
|
142
155
|
constructor(options) {
|
|
@@ -152,6 +165,7 @@ class FcodeGraphQLClient {
|
|
|
152
165
|
headers: {
|
|
153
166
|
'Fcode-Team': options.teamSlug,
|
|
154
167
|
authorization: `Bearer ${options.accessToken}`,
|
|
168
|
+
[cli_version_1.CLI_VERSION_HEADER]: (0, cli_version_1.cliVersion)(),
|
|
155
169
|
},
|
|
156
170
|
});
|
|
157
171
|
}
|
|
@@ -1039,27 +1053,37 @@ class FcodeGraphQLClient {
|
|
|
1039
1053
|
return process;
|
|
1040
1054
|
}
|
|
1041
1055
|
/**
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
* undefined.
|
|
1056
|
+
* The team's inheritable settings, each read from its `{ mode, value, from }`: the value in effect
|
|
1057
|
+
* and where it comes from. An API that predates settings inheritance has no such field, and leaves
|
|
1058
|
+
* `origins` undefined so the settings commands refuse instead of reading every setting as inherited.
|
|
1045
1059
|
*/
|
|
1046
1060
|
mapTeamSettings(team) {
|
|
1047
1061
|
const settings = {
|
|
1048
1062
|
slug: team.slug,
|
|
1049
|
-
zoneId: team.zoneId,
|
|
1050
|
-
errorHandlerConfig: team.errorHandlerConfig,
|
|
1051
1063
|
parentTeams: team.parentTeams ?? [],
|
|
1052
1064
|
};
|
|
1053
|
-
const
|
|
1065
|
+
const inheritable = team.inheritableSettings;
|
|
1066
|
+
if (!inheritable) {
|
|
1067
|
+
return settings;
|
|
1068
|
+
}
|
|
1069
|
+
settings.zoneId = inheritable.zoneId?.value ?? undefined;
|
|
1070
|
+
settings.errorHandlerConfig = inheritable.errorHandlerConfig?.value ?? null;
|
|
1071
|
+
settings.primaryLocale = inheritable.primaryLocale?.value ?? null;
|
|
1072
|
+
const webhookAuth = inheritable.webhookAuth?.value;
|
|
1054
1073
|
if (webhookAuth?.variableKey) {
|
|
1055
1074
|
settings.webhookAuth = { variableKey: webhookAuth.variableKey };
|
|
1056
1075
|
if (webhookAuth.headerName && webhookAuth.headerName !== types_1.DEFAULT_WEBHOOK_AUTH_HEADER) {
|
|
1057
1076
|
settings.webhookAuth.headerName = webhookAuth.headerName;
|
|
1058
1077
|
}
|
|
1059
1078
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1079
|
+
const origin = (setting) => ({ mode: setting?.mode ?? 'INHERIT', from: setting?.from ?? null });
|
|
1080
|
+
settings.origins = {
|
|
1081
|
+
ZONE_ID: origin(inheritable.zoneId),
|
|
1082
|
+
PARAMS_SCHEMA_VALIDATION: origin(inheritable.paramsSchemaValidationEnabled),
|
|
1083
|
+
ERROR_HANDLER: origin(inheritable.errorHandlerConfig),
|
|
1084
|
+
WEBHOOK_AUTH: origin(inheritable.webhookAuth),
|
|
1085
|
+
PRIMARY_LOCALE: origin(inheritable.primaryLocale),
|
|
1086
|
+
};
|
|
1063
1087
|
return settings;
|
|
1064
1088
|
}
|
|
1065
1089
|
mapTeamVersion(teamVersion) {
|
|
@@ -1195,28 +1219,17 @@ class FcodeGraphQLClient {
|
|
|
1195
1219
|
query team($slug: ID!) {
|
|
1196
1220
|
team(slug: $slug) {
|
|
1197
1221
|
slug
|
|
1198
|
-
zoneId
|
|
1199
|
-
errorHandlerConfig {
|
|
1200
|
-
processId
|
|
1201
|
-
tag
|
|
1202
|
-
}
|
|
1203
1222
|
parentTeams {
|
|
1204
1223
|
slug
|
|
1205
1224
|
version
|
|
1206
1225
|
}
|
|
1207
|
-
|
|
1208
|
-
webhookAuth {
|
|
1209
|
-
headerName
|
|
1210
|
-
variableKey
|
|
1211
|
-
}
|
|
1212
|
-
primaryLocale
|
|
1213
|
-
}
|
|
1226
|
+
${INHERITABLE_SETTINGS_SELECTION}
|
|
1214
1227
|
}
|
|
1215
1228
|
}
|
|
1216
1229
|
`, { slug: this.teamSlug })
|
|
1217
1230
|
.then(({ team }) => this.mapTeamSettings(team))
|
|
1218
1231
|
.catch((error) => {
|
|
1219
|
-
return Promise.reject(this.
|
|
1232
|
+
return Promise.reject(this.cleanTeamSettingsError(error));
|
|
1220
1233
|
});
|
|
1221
1234
|
}
|
|
1222
1235
|
pushTeam(update) {
|
|
@@ -1225,30 +1238,32 @@ class FcodeGraphQLClient {
|
|
|
1225
1238
|
mutation updateTeam($slug: String!, $update: TeamUpdateInput!) {
|
|
1226
1239
|
updateTeam(slug: $slug, update: $update) {
|
|
1227
1240
|
slug
|
|
1228
|
-
zoneId
|
|
1229
|
-
errorHandlerConfig {
|
|
1230
|
-
processId
|
|
1231
|
-
tag
|
|
1232
|
-
}
|
|
1233
1241
|
parentTeams {
|
|
1234
1242
|
slug
|
|
1235
1243
|
version
|
|
1236
1244
|
}
|
|
1237
|
-
|
|
1238
|
-
webhookAuth {
|
|
1239
|
-
headerName
|
|
1240
|
-
variableKey
|
|
1241
|
-
}
|
|
1242
|
-
primaryLocale
|
|
1243
|
-
}
|
|
1245
|
+
${INHERITABLE_SETTINGS_SELECTION}
|
|
1244
1246
|
}
|
|
1245
1247
|
}
|
|
1246
1248
|
`, { slug: this.teamSlug, update })
|
|
1247
1249
|
.then(({ updateTeam: team }) => this.mapTeamSettings(team))
|
|
1248
1250
|
.catch((error) => {
|
|
1249
|
-
return Promise.reject(this.
|
|
1251
|
+
return Promise.reject(this.cleanTeamSettingsError(error));
|
|
1250
1252
|
});
|
|
1251
1253
|
}
|
|
1254
|
+
/**
|
|
1255
|
+
* An API that predates settings inheritance rejects the query naming `inheritableSettings` as a
|
|
1256
|
+
* schema error; that reads better as what it means than as GraphQL validation output.
|
|
1257
|
+
*/
|
|
1258
|
+
cleanTeamSettingsError(error) {
|
|
1259
|
+
const message = error?.response?.errors?.[0]?.message ?? '';
|
|
1260
|
+
// Only the schema errors an older API answers with (an unknown output field, an unknown input
|
|
1261
|
+
// field): the API's own validation errors also name inheritableSettings, and mean something else.
|
|
1262
|
+
if (message.includes('inheritableSettings') && UNKNOWN_FIELD_ERROR.test(message)) {
|
|
1263
|
+
return new errors_1.PlatformPredatesSettingsInheritance();
|
|
1264
|
+
}
|
|
1265
|
+
return this.cleanError(error);
|
|
1266
|
+
}
|
|
1252
1267
|
mapProgrammingLanguageManifest(programmingLanguageManifest) {
|
|
1253
1268
|
const allDependencies = programmingLanguageManifest.nextInstallation?.dependencies || programmingLanguageManifest.dependencies;
|
|
1254
1269
|
return new types_1.ProgrammingLanguageManifest(programmingLanguageManifest.id, programmingLanguageManifest.programmingLanguage, programmingLanguageManifest.programmingLanguage, this.mapDependencies(allDependencies),
|
|
@@ -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);
|
|
@@ -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