@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 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.5.0 linux-x64 node-v22.22.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
@@ -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 settings.json does not have, is rejected.
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;
@@ -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 settings.json does not have, is rejected.
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
- const update = { parentTeams: settings.parentTeams };
27
- if (settings.zoneId !== undefined) {
28
- update.zoneId = settings.zoneId;
28
+ if (await this.pulledByAPreInheritanceCli(settings, storedHash)) {
29
+ throw new errors_1.PreInheritanceSettingsFile();
29
30
  }
30
- if (settings.errorHandlerConfig) {
31
- const processId = await this._settingsRepository.resolveSlugToId(settings.errorHandlerConfig.processSlug);
32
- update.executionErrorHandlerConfig = { processId, tag: settings.errorHandlerConfig.tag ?? null };
33
- }
34
- // Sent unconditionally, null arm included: settings.json is the source of truth, so removing
35
- // webhookAuth from it must clear the cloud configuration rather than leave it dangling.
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
  }
@@ -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();
@@ -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
+ }
@@ -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;
@@ -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 — the same chain the
614
- // cloud resolves, so a local run answers a regional variant the same way.
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
- for (let candidate = requested; candidate; ) {
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;
@@ -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
- /** The workspace's settings.json: what `fcode settings:pull|push` syncs against the cloud team. */
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 TeamUpdateInput {
438
+ export interface InheritedWorkspaceSettings {
427
439
  zoneId?: string;
428
- parentTeams?: ParentTeamRef[];
429
- executionErrorHandlerConfig?: RawErrorHandlerConfig | null;
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;
@@ -261,7 +261,11 @@ class ProgrammingLanguageManifest extends Resource {
261
261
  }
262
262
  }
263
263
  exports.ProgrammingLanguageManifest = ProgrammingLanguageManifest;
264
- /** The workspace's settings.json: what `fcode settings:pull|push` syncs against the cloud team. */
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
- /** Builds the settings from the cloud's raw payload, resolving the error-handler id to a slug. */
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