@factorialco/fcode-cli 2.0.0 → 2.2.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.
Files changed (76) hide show
  1. package/README.md +248 -2
  2. package/dist/commands/add.js +3 -1
  3. package/dist/commands/clone.d.ts +1 -0
  4. package/dist/commands/clone.js +5 -0
  5. package/dist/commands/i18n/add.d.ts +9 -0
  6. package/dist/commands/i18n/add.js +13 -0
  7. package/dist/commands/i18n/pull.d.ts +12 -0
  8. package/dist/commands/i18n/pull.js +14 -0
  9. package/dist/commands/i18n/push.d.ts +12 -0
  10. package/dist/commands/i18n/push.js +14 -0
  11. package/dist/commands/i18n/remove.d.ts +9 -0
  12. package/dist/commands/i18n/remove.js +19 -0
  13. package/dist/commands/i18n/reset.d.ts +9 -0
  14. package/dist/commands/i18n/reset.js +13 -0
  15. package/dist/commands/i18n/status.d.ts +11 -0
  16. package/dist/commands/i18n/status.js +42 -0
  17. package/dist/commands/pull.js +5 -3
  18. package/dist/commands/push.js +5 -3
  19. package/dist/commands/reset.js +3 -1
  20. package/dist/commands/run.d.ts +1 -0
  21. package/dist/commands/run.js +5 -2
  22. package/dist/commands/setup-debug.js +2 -2
  23. package/dist/commands/status.js +4 -2
  24. package/dist/commands/team/aliases/delete.d.ts +9 -0
  25. package/dist/commands/team/aliases/delete.js +28 -0
  26. package/dist/commands/team/aliases/list.d.ts +6 -0
  27. package/dist/commands/team/aliases/list.js +23 -0
  28. package/dist/commands/team/aliases/set.d.ts +10 -0
  29. package/dist/commands/team/aliases/set.js +25 -0
  30. package/dist/commands/team/pull.js +8 -1
  31. package/dist/commands/team/push.js +6 -0
  32. package/dist/commands/team/versions/create.d.ts +13 -0
  33. package/dist/commands/team/versions/create.js +42 -0
  34. package/dist/commands/team/versions/delete.d.ts +12 -0
  35. package/dist/commands/team/versions/delete.js +32 -0
  36. package/dist/commands/team/versions/list.d.ts +6 -0
  37. package/dist/commands/team/versions/list.js +25 -0
  38. package/dist/commands/variables/status.js +11 -0
  39. package/dist/config/remote.js +1 -1
  40. package/dist/config/settings.d.ts +1 -0
  41. package/dist/config/settings.js +6 -0
  42. package/dist/config/workspace.js +10 -0
  43. package/dist/core/commands/i18n-command.d.ts +5 -0
  44. package/dist/core/commands/i18n-command.js +16 -0
  45. package/dist/core/errors.d.ts +6 -0
  46. package/dist/core/errors.js +19 -1
  47. package/dist/core/executors/execution-task.js +4 -0
  48. package/dist/core/executors/javascript-execution-task.js +26 -1
  49. package/dist/core/resource-pusher.js +15 -0
  50. package/dist/core/types.d.ts +66 -2
  51. package/dist/core/types.js +45 -3
  52. package/dist/env.d.ts +2 -2
  53. package/dist/env.js +1 -1
  54. package/dist/repositories/locale.d.ts +24 -0
  55. package/dist/repositories/locale.js +102 -0
  56. package/dist/repositories/resource.js +4 -1
  57. package/dist/repositories/team.d.ts +7 -1
  58. package/dist/repositories/team.js +39 -2
  59. package/dist/repositories/variable.d.ts +24 -0
  60. package/dist/repositories/variable.js +70 -22
  61. package/dist/runtime/executor.js +2 -2
  62. package/dist/runtime/executor.py +2 -2
  63. package/dist/runtime/libs/python/fcode/__init__.py +3 -2
  64. package/dist/runtime/libs/python/fcode/context.py +43 -1
  65. package/dist/services/fcode-graphql-client.d.ts +20 -1
  66. package/dist/services/fcode-graphql-client.js +276 -17
  67. package/dist/services/locale-merger.d.ts +23 -0
  68. package/dist/services/locale-merger.js +111 -0
  69. package/dist/services/locale-resolver.d.ts +18 -0
  70. package/dist/services/locale-resolver.js +120 -0
  71. package/dist/services/process-runner.d.ts +1 -0
  72. package/dist/services/process-runner.js +2 -2
  73. package/dist/utils/gitignore.d.ts +2 -0
  74. package/dist/utils/gitignore.js +9 -0
  75. package/oclif.manifest.json +511 -127
  76. package/package.json +11 -2
@@ -41,6 +41,9 @@ export interface TeamMetadata {
41
41
  variables: {
42
42
  [key: string]: Variable;
43
43
  };
44
+ i18n: {
45
+ [key: string]: Locale;
46
+ };
44
47
  dependencies: {
45
48
  [key: string]: ProgrammingLanguageManifest;
46
49
  };
@@ -187,6 +190,27 @@ export declare class ScriptLibrary extends Resource {
187
190
  constructor(id: string, name: string, sourceCode: string, programmingLanguage: ProgrammingLanguage, resolvingTeamSlug?: string);
188
191
  get contentHash(): string;
189
192
  }
193
+ export declare class Locale extends Resource {
194
+ static LOCALE_REGEX: RegExp;
195
+ static INHERITED_SUFFIX: string;
196
+ id: string | undefined;
197
+ slug: string;
198
+ locale: string;
199
+ content: string;
200
+ constructor(id: string | undefined, locale: string, content: string, resolvingTeamSlug?: string);
201
+ /**
202
+ * The name a locale is tracked and stored under. Unlike every other resource, the identifier is
203
+ * not unique: a workspace sees its own `en` and each parent workspace's `en`, and all of them are
204
+ * live, because an override is layered over the parent's file key by key rather than replacing
205
+ * it. Naming files after the identifier alone would keep only one of them.
206
+ *
207
+ * A workspace directory therefore holds two files at most per identifier: `en.yaml` for its own
208
+ * and `en.inherited.yaml` for everything it inherits, which `mergeInheritedLocales` collapses
209
+ * into one file. An identifier cannot contain a dot, so the name parses back.
210
+ */
211
+ static slugFor(locale: string, resolvingTeamSlug?: string): string;
212
+ get contentHash(): string;
213
+ }
190
214
  export declare class VersionedScriptLibrary extends Resource {
191
215
  id: string;
192
216
  slug: string;
@@ -205,7 +229,7 @@ export declare class Variable extends Resource {
205
229
  key: string;
206
230
  value: string;
207
231
  overriddenLocally?: boolean;
208
- constructor(id: string | undefined, key: string, value: string, isSensitive: boolean);
232
+ constructor(id: string | undefined, key: string, value: string, isSensitive: boolean, resolvingTeamSlug?: string);
209
233
  static validate(variable: Variable): Variable;
210
234
  get contentHash(): string;
211
235
  }
@@ -221,6 +245,40 @@ export interface ErrorHandlerConfig {
221
245
  processSlug: string;
222
246
  tag?: string | null;
223
247
  }
248
+ export interface TeamVersionManifestEntry {
249
+ resourceType: 'MODULE' | 'PROCESS';
250
+ resourceId: string;
251
+ slug: string;
252
+ outcome: 'CREATED' | 'FAILED' | 'SKIPPED_EXISTS' | 'SKIPPED_NO_TAG' | 'UPDATED';
253
+ error?: string | null;
254
+ }
255
+ export interface TeamVersion {
256
+ id: string;
257
+ tag: string;
258
+ comment?: string | null;
259
+ manifest: TeamVersionManifestEntry[];
260
+ createdAt: string;
261
+ createdBy?: {
262
+ id: string;
263
+ name: string;
264
+ } | null;
265
+ }
266
+ export interface TeamVersionAlias {
267
+ id: string;
268
+ name: string;
269
+ tag: string;
270
+ createdAt: string;
271
+ updatedAt?: string | null;
272
+ }
273
+ export interface TeamVersionFileEntry {
274
+ tag: string;
275
+ comment?: string;
276
+ createdAt: string;
277
+ }
278
+ export interface TeamAliasFileEntry {
279
+ name: string;
280
+ tag: string;
281
+ }
224
282
  export declare class Team extends Resource {
225
283
  id: string | undefined;
226
284
  slug: string;
@@ -228,7 +286,10 @@ export declare class Team extends Resource {
228
286
  errorHandlerConfig?: ErrorHandlerConfig;
229
287
  parentTeamSlugs: string[];
230
288
  webhookAuth?: WebhookAuth;
231
- constructor(slug: string, parentTeamSlugs: string[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth);
289
+ primaryLocale?: string;
290
+ versions?: TeamVersionFileEntry[];
291
+ aliases?: TeamAliasFileEntry[];
292
+ constructor(slug: string, parentTeamSlugs: string[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth, primaryLocale?: string);
232
293
  get contentHash(): string;
233
294
  }
234
295
  export interface RawErrorHandlerConfig {
@@ -241,6 +302,7 @@ export interface RawTeamSettings {
241
302
  errorHandlerConfig?: RawErrorHandlerConfig | null;
242
303
  parentTeamSlugs: string[];
243
304
  webhookAuth?: WebhookAuth | null;
305
+ primaryLocale?: string | null;
244
306
  }
245
307
  export interface TeamUpdateInput {
246
308
  zoneId?: string;
@@ -250,6 +312,7 @@ export interface TeamUpdateInput {
250
312
  headerName?: string | null;
251
313
  variableKey?: string | null;
252
314
  } | null;
315
+ primaryLocale?: string;
253
316
  }
254
317
  export type ResourceMetadata = {
255
318
  [key: string]: any;
@@ -281,6 +344,7 @@ export type ExecutionOptions = {
281
344
  hookFilePath?: string;
282
345
  hookFunction?: string;
283
346
  silent?: boolean;
347
+ locale?: string;
284
348
  };
285
349
  export declare const ForceFlag: {
286
350
  force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServiceAccount = exports.Dependency = exports.DependenciesFlags = exports.TeamNameArg = exports.ResourceSlugArg = exports.ForceFlag = exports.ScriptLibraryNotFound = exports.ProcessNotFound = exports.Team = exports.ProgrammingLanguageManifest = exports.Variable = exports.VersionedScriptLibrary = exports.ScriptLibrary = exports.VersionedProcess = exports.Process = exports.DEFAULT_WEBHOOK_AUTH_HEADER = exports.programmingLanguageDisplayName = exports.ResourceStatus = exports.OnConflictResolution = exports.Resource = void 0;
3
+ exports.ServiceAccount = exports.Dependency = exports.DependenciesFlags = exports.TeamNameArg = exports.ResourceSlugArg = exports.ForceFlag = exports.ScriptLibraryNotFound = exports.ProcessNotFound = exports.Team = exports.ProgrammingLanguageManifest = exports.Variable = exports.VersionedScriptLibrary = exports.Locale = exports.ScriptLibrary = exports.VersionedProcess = exports.Process = exports.DEFAULT_WEBHOOK_AUTH_HEADER = exports.programmingLanguageDisplayName = exports.ResourceStatus = exports.OnConflictResolution = exports.Resource = void 0;
4
4
  const core_1 = require("@oclif/core");
5
5
  const crypto = require("node:crypto");
6
6
  const canonical_hash_1 = require("../utils/canonical-hash");
@@ -103,6 +103,41 @@ class ScriptLibrary extends Resource {
103
103
  }
104
104
  }
105
105
  exports.ScriptLibrary = ScriptLibrary;
106
+ class Locale extends Resource {
107
+ constructor(id, locale, content, resolvingTeamSlug) {
108
+ super();
109
+ this.id = id;
110
+ this.slug = Locale.slugFor(locale, resolvingTeamSlug);
111
+ this.locale = locale;
112
+ this.content = content;
113
+ this.resolvingTeamSlug = resolvingTeamSlug;
114
+ }
115
+ /**
116
+ * The name a locale is tracked and stored under. Unlike every other resource, the identifier is
117
+ * not unique: a workspace sees its own `en` and each parent workspace's `en`, and all of them are
118
+ * live, because an override is layered over the parent's file key by key rather than replacing
119
+ * it. Naming files after the identifier alone would keep only one of them.
120
+ *
121
+ * A workspace directory therefore holds two files at most per identifier: `en.yaml` for its own
122
+ * and `en.inherited.yaml` for everything it inherits, which `mergeInheritedLocales` collapses
123
+ * into one file. An identifier cannot contain a dot, so the name parses back.
124
+ */
125
+ static slugFor(locale, resolvingTeamSlug) {
126
+ return resolvingTeamSlug ? `${locale}${Locale.INHERITED_SUFFIX}` : locale;
127
+ }
128
+ // Hashed over the raw YAML: the file is the source of truth and round-trips comments and
129
+ // ordering, so any edit — even one that parses to the same translations — is a change to push.
130
+ get contentHash() {
131
+ return crypto.createHash('md5').update(this.content).digest('hex');
132
+ }
133
+ }
134
+ exports.Locale = Locale;
135
+ // Free-form identifier rather than a validated BCP-47 tag, so a workspace can use whatever it
136
+ // already names its translations after. Case is significant: pt-BR and pt-br are different
137
+ // locales, and the platform's unique index is case-sensitive too.
138
+ Locale.LOCALE_REGEX = /^[A-Za-z0-9][\w-]{0,19}$/;
139
+ // Marks a file as owned by a parent workspace, the way `variables.inherited.env` does.
140
+ Locale.INHERITED_SUFFIX = '.inherited';
106
141
  class VersionedScriptLibrary extends Resource {
107
142
  constructor(id, tag, sourceCode, programmingLanguage) {
108
143
  super();
@@ -118,13 +153,14 @@ class VersionedScriptLibrary extends Resource {
118
153
  }
119
154
  exports.VersionedScriptLibrary = VersionedScriptLibrary;
120
155
  class Variable extends Resource {
121
- constructor(id, key, value, isSensitive) {
156
+ constructor(id, key, value, isSensitive, resolvingTeamSlug) {
122
157
  super();
123
158
  this.id = id;
124
159
  this.slug = key;
125
160
  this.key = key;
126
161
  this.value = value;
127
162
  this.isSensitive = isSensitive;
163
+ this.resolvingTeamSlug = resolvingTeamSlug;
128
164
  }
129
165
  static validate(variable) {
130
166
  if (!Variable.ENV_KEY_REGEX.test(variable.key)) {
@@ -165,13 +201,14 @@ class ProgrammingLanguageManifest extends Resource {
165
201
  }
166
202
  exports.ProgrammingLanguageManifest = ProgrammingLanguageManifest;
167
203
  class Team extends Resource {
168
- constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig, webhookAuth) {
204
+ constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig, webhookAuth, primaryLocale) {
169
205
  super();
170
206
  this.slug = slug;
171
207
  this.parentTeamSlugs = parentTeamSlugs;
172
208
  this.zoneId = zoneId;
173
209
  this.errorHandlerConfig = errorHandlerConfig;
174
210
  this.webhookAuth = webhookAuth;
211
+ this.primaryLocale = primaryLocale;
175
212
  }
176
213
  get contentHash() {
177
214
  const payload = {
@@ -185,6 +222,11 @@ class Team extends Resource {
185
222
  if (this.webhookAuth) {
186
223
  payload.webhookAuth = this.webhookAuth;
187
224
  }
225
+ // Same insert-only-when-set rule, for the same reason: a team.json written before locales
226
+ // existed must keep the hash already stored in .fcode.
227
+ if (this.primaryLocale) {
228
+ payload.primaryLocale = this.primaryLocale;
229
+ }
188
230
  return (0, canonical_hash_1.canonicalJsonHash)(payload);
189
231
  }
190
232
  }
package/dist/env.d.ts CHANGED
@@ -27,7 +27,7 @@ export declare const envSchema: z.ZodObject<{
27
27
  false: "false";
28
28
  }>>;
29
29
  CLI_UX_SKIP_TTY_CHECK: z.ZodOptional<z.ZodString>;
30
- YC_COMMAND: z.ZodOptional<z.ZodString>;
30
+ FC_COMMAND: z.ZodOptional<z.ZodString>;
31
31
  }, z.core.$strip>;
32
32
  export declare const env: {
33
33
  FCODE_CLOUD_URL: string;
@@ -38,6 +38,6 @@ export declare const env: {
38
38
  FCODE_DISABLE_UPDATE_NOTIFIER?: "true" | "false" | undefined;
39
39
  SILENT_MODE?: "true" | "false" | undefined;
40
40
  CLI_UX_SKIP_TTY_CHECK?: string | undefined;
41
- YC_COMMAND?: string | undefined;
41
+ FC_COMMAND?: string | undefined;
42
42
  };
43
43
  export default env;
package/dist/env.js CHANGED
@@ -25,7 +25,7 @@ exports.envSchema = zod_1.z.object({
25
25
  SILENT_MODE: zod_1.z.enum(['true', 'false']).optional(),
26
26
  CLI_UX_SKIP_TTY_CHECK: zod_1.z.string().min(1).optional(),
27
27
  // --- Debug tooling ---
28
- YC_COMMAND: zod_1.z.string().min(1).optional(),
28
+ FC_COMMAND: zod_1.z.string().min(1).optional(),
29
29
  });
30
30
  /** Treat empty strings (e.g. an unset shell/dotenv var rendered as "") as missing. */
31
31
  const raw = Object.fromEntries(Object.entries(process.env).filter(([, value]) => value !== ''));
@@ -0,0 +1,24 @@
1
+ import { Locale, ResourceMetadata } from '../core/types';
2
+ import ResourceRepositoryBase from './resource';
3
+ /**
4
+ * Locales live as one YAML file per locale directly under `i18n/`, named after the slug
5
+ * (`i18n/pt-BR.yaml`, `i18n/pt-BR.inherited.yaml`). A file, not a directory: a locale has exactly
6
+ * one artifact, and naming the file after the identifier is what lets `push` work without knowing
7
+ * any ids.
8
+ *
9
+ * A workspace keeps what it inherits next to what it owns, because overriding a locale only layers
10
+ * keys over the inherited file rather than replacing it — see {@link Locale.slugFor}.
11
+ */
12
+ declare class LocaleRepository extends ResourceRepositoryBase<Locale> {
13
+ constructor();
14
+ find(slug: string): Promise<Locale>;
15
+ findSlugsNotAdded(): Promise<Array<string>>;
16
+ protected getResourcePath(locale: Locale): string;
17
+ protected removeFiles(locale: Locale): Promise<void>;
18
+ protected resourceToMetadata(locale: Locale): ResourceMetadata;
19
+ protected saveFiles(locale: Locale): Promise<void>;
20
+ metadataFromFiles(slug: string): Promise<ResourceMetadata>;
21
+ validSlug(slug: string): boolean;
22
+ slugFormatMessage(): string;
23
+ }
24
+ export default LocaleRepository;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs = require("fs-extra");
4
+ const path = require("node:path");
5
+ const color_1 = require("@oclif/color");
6
+ const cli_1 = require("../utils/cli");
7
+ const errors_1 = require("../core/errors");
8
+ const types_1 = require("../core/types");
9
+ const file_utils_1 = require("../utils/file-utils");
10
+ const resource_1 = require("./resource");
11
+ const YAML_EXTENSION = '.yaml';
12
+ /**
13
+ * Locales live as one YAML file per locale directly under `i18n/`, named after the slug
14
+ * (`i18n/pt-BR.yaml`, `i18n/pt-BR.inherited.yaml`). A file, not a directory: a locale has exactly
15
+ * one artifact, and naming the file after the identifier is what lets `push` work without knowing
16
+ * any ids.
17
+ *
18
+ * A workspace keeps what it inherits next to what it owns, because overriding a locale only layers
19
+ * keys over the inherited file rather than replacing it — see {@link Locale.slugFor}.
20
+ */
21
+ class LocaleRepository extends resource_1.default {
22
+ constructor() {
23
+ super({
24
+ name: 'i18n',
25
+ });
26
+ }
27
+ async find(slug) {
28
+ const { [this._repositoryName]: locales } = await this.workspaceConfig.getActiveTeam();
29
+ const localeMetadata = locales?.[slug];
30
+ if (!localeMetadata) {
31
+ throw new errors_1.LocalLocaleNotFound(new Error('Locale not found on team metadata'), slug);
32
+ }
33
+ try {
34
+ const content = fs.readFileSync(this.getResourcePath(localeMetadata)).toString();
35
+ return new types_1.Locale(localeMetadata.id, localeMetadata.locale ?? slug, content, localeMetadata.resolvingTeamSlug);
36
+ }
37
+ catch (error) {
38
+ throw new errors_1.LocalLocaleNotFound(error, slug);
39
+ }
40
+ }
41
+ async findSlugsNotAdded() {
42
+ const slugsNotAdded = [];
43
+ if (!fs.pathExistsSync(this._repositoryName)) {
44
+ return slugsNotAdded;
45
+ }
46
+ for (const fileName of fs.readdirSync(this._repositoryName)) {
47
+ if (!fileName.endsWith(YAML_EXTENSION)) {
48
+ continue;
49
+ }
50
+ const slug = path.basename(fileName, YAML_EXTENSION);
51
+ if (await this.workspaceConfig.exists('i18n', slug)) {
52
+ continue;
53
+ }
54
+ // Only `pull` writes an inherited file, and it registers it as it does. One left behind by a
55
+ // workspace that no longer inherits it is stale, not a locale waiting to be tracked.
56
+ if (slug.endsWith(types_1.Locale.INHERITED_SUFFIX)) {
57
+ continue;
58
+ }
59
+ if (this.validSlug(slug)) {
60
+ slugsNotAdded.push(slug);
61
+ }
62
+ else {
63
+ cli_1.default.log(color_1.default.yellow(`\n⚠️ local locale file '${fileName}' doesn't have a valid name and will be ignored. ${this.slugFormatMessage()}`));
64
+ }
65
+ }
66
+ return slugsNotAdded;
67
+ }
68
+ getResourcePath(locale) {
69
+ return path.join(this._repositoryName, `${locale.slug ?? locale.locale}${YAML_EXTENSION}`);
70
+ }
71
+ async removeFiles(locale) {
72
+ fs.removeSync(this.getResourcePath(locale));
73
+ this.removeDirectoryIfEmpty(this._repositoryName);
74
+ }
75
+ resourceToMetadata(locale) {
76
+ return {
77
+ id: locale.id,
78
+ locale: locale.locale,
79
+ slug: locale.slug,
80
+ ...(locale.resolvingTeamSlug && { resolvingTeamSlug: locale.resolvingTeamSlug }),
81
+ };
82
+ }
83
+ async saveFiles(locale) {
84
+ // Inherited locales are written read-only, like inherited modules: they belong to a parent
85
+ // workspace, and editing one here would push an override rather than change the parent.
86
+ (0, file_utils_1.outputFileSyncIfChanged)(this.getResourcePath(locale), locale.content || '', { readOnly: Boolean(locale.resolvingTeamSlug) });
87
+ }
88
+ async metadataFromFiles(slug) {
89
+ return {
90
+ locale: slug,
91
+ slug: slug,
92
+ contentHash: '',
93
+ };
94
+ }
95
+ validSlug(slug) {
96
+ return types_1.Locale.LOCALE_REGEX.test(slug);
97
+ }
98
+ slugFormatMessage() {
99
+ return "Use up to 20 letters, numbers, '-' or '_', starting with a letter or a number.";
100
+ }
101
+ }
102
+ exports.default = LocaleRepository;
@@ -19,7 +19,10 @@ class ResourceRepositoryBase {
19
19
  }
20
20
  }
21
21
  async findAll() {
22
- const { [this._repositoryName]: resources } = await this.workspaceConfig.getActiveTeam();
22
+ // Defaulted because a collection added after a workspace was cloned (as `i18n` was) is absent
23
+ // from its sync metadata until the first pull writes it — findAll must see "no resources",
24
+ // not crash every aggregate pull/push/status in every pre-existing workspace.
25
+ const { [this._repositoryName]: resources = {} } = await this.workspaceConfig.getActiveTeam();
23
26
  // eslint-disable-next-line unicorn/no-array-reduce
24
27
  const existResources = await Object.keys(resources).reduce((promise, slug) => promise.then(async (existIds) => {
25
28
  const exists = await this.exists(slug);
@@ -1,4 +1,4 @@
1
- import { RawTeamSettings, Team } from '../core/types';
1
+ import { RawTeamSettings, Team, TeamVersion, TeamVersionAlias } from '../core/types';
2
2
  import WorkspaceConfig from '../config/workspace';
3
3
  /**
4
4
  * Reads/writes the singleton team.json (timezone, error handler, inheritance, webhook auth) at the
@@ -16,6 +16,12 @@ export default class TeamRepository {
16
16
  save(team: Team): void;
17
17
  /** Builds a Team from the cloud's raw settings, resolving the error-handler id to a slug. */
18
18
  fromRaw(raw: RawTeamSettings): Promise<Team>;
19
+ /**
20
+ * Writes the cloud's team versions and aliases into the Team as the pull-only team.json
21
+ * fields (sorted for stable git diffs). These fields never enter the content hash and are
22
+ * stripped from pushes: all writes go through the team:versions/team:aliases commands.
23
+ */
24
+ applyCloudVersioning(team: Team, versions: TeamVersion[], aliases: TeamVersionAlias[]): void;
19
25
  resolveSlugToId(processSlug: string): Promise<string>;
20
26
  resolveIdToSlug(processId: string): Promise<string>;
21
27
  private parseErrorHandler;
@@ -29,7 +29,15 @@ class TeamRepository {
29
29
  }
30
30
  catch { }
31
31
  const parentTeamSlugs = Array.isArray(raw.parentTeamSlugs) ? raw.parentTeamSlugs : [];
32
- return new types_1.Team(slug, parentTeamSlugs, raw.zoneId, this.parseErrorHandler(raw.errorHandlerConfig), this.parseWebhookAuth(raw.webhookAuth));
32
+ const team = new types_1.Team(slug, parentTeamSlugs, raw.zoneId, this.parseErrorHandler(raw.errorHandlerConfig), this.parseWebhookAuth(raw.webhookAuth), typeof raw.primaryLocale === 'string' && raw.primaryLocale.length > 0 ? raw.primaryLocale : undefined);
33
+ // Parsed so a find()+save() round-trip does not drop the pull-only fields.
34
+ if (Array.isArray(raw.versions)) {
35
+ team.versions = raw.versions;
36
+ }
37
+ if (Array.isArray(raw.aliases)) {
38
+ team.aliases = raw.aliases;
39
+ }
40
+ return team;
33
41
  }
34
42
  save(team) {
35
43
  (0, file_utils_1.outputFileSyncIfChanged)(TEAM_FILENAME, JSON.stringify(this.toFile(team), null, 2));
@@ -43,7 +51,26 @@ class TeamRepository {
43
51
  tag: raw.errorHandlerConfig.tag ?? null,
44
52
  };
45
53
  }
46
- return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig, this.parseWebhookAuth(raw.webhookAuth));
54
+ return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig, this.parseWebhookAuth(raw.webhookAuth), raw.primaryLocale ?? undefined);
55
+ }
56
+ /**
57
+ * Writes the cloud's team versions and aliases into the Team as the pull-only team.json
58
+ * fields (sorted for stable git diffs). These fields never enter the content hash and are
59
+ * stripped from pushes: all writes go through the team:versions/team:aliases commands.
60
+ */
61
+ applyCloudVersioning(team, versions, aliases) {
62
+ team.versions = versions
63
+ .map((version) => {
64
+ const entry = { tag: version.tag, createdAt: version.createdAt };
65
+ if (version.comment) {
66
+ entry.comment = version.comment;
67
+ }
68
+ return entry;
69
+ })
70
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt));
71
+ team.aliases = aliases
72
+ .map((alias) => ({ name: alias.name, tag: alias.tag }))
73
+ .sort((a, b) => a.name.localeCompare(b.name));
47
74
  }
48
75
  async resolveSlugToId(processSlug) {
49
76
  const { processes } = await this.workspaceConfig.getActiveTeam();
@@ -84,6 +111,16 @@ class TeamRepository {
84
111
  if (team.webhookAuth !== undefined) {
85
112
  file.webhookAuth = team.webhookAuth;
86
113
  }
114
+ if (team.primaryLocale !== undefined) {
115
+ file.primaryLocale = team.primaryLocale;
116
+ }
117
+ // Pull-only global versioning fields, appended after the pushable settings.
118
+ if (team.versions !== undefined) {
119
+ file.versions = team.versions;
120
+ }
121
+ if (team.aliases !== undefined) {
122
+ file.aliases = team.aliases;
123
+ }
87
124
  return file;
88
125
  }
89
126
  }
@@ -4,16 +4,40 @@ export declare const DEFAULT_SENSITIVE_VALUE = "********";
4
4
  declare class VariableRepository extends ResourceRepositoryBase<Variable> {
5
5
  private filename;
6
6
  private localFilename;
7
+ private inheritedFilename;
7
8
  private filenameInitPromise;
8
9
  constructor();
9
10
  private ensureFilenamesInitialized;
10
11
  findAll(): Promise<Variable[]>;
12
+ /**
13
+ * Values as a local run sees them. Precedence, lowest first: variables inherited from
14
+ * parent workspaces, this workspace's own variables, and the local overrides file — the
15
+ * same order the platform applies, where a workspace's own value wins over an inherited one.
16
+ * An empty value is not an override but an unset one: the platform rejects blank values, and
17
+ * a variable emptied in variables.env is a removal (see `mapResourceStatus`), so an empty
18
+ * entry falls through to the layer below rather than blanking it.
19
+ */
11
20
  findAllForExecution(): Promise<Variable[]>;
21
+ /**
22
+ * An inherited key the workspace has given a value of its own, overriding it. An empty entry
23
+ * does not count: the platform has no empty values, so it reads as "not set" here too.
24
+ */
25
+ private overridesInherited;
12
26
  find(slug: string): Promise<Variable>;
27
+ /**
28
+ * Keys declared in the workspace's own variables file that its metadata does not claim yet.
29
+ * A key the workspace inherits counts as one of those: the metadata still points at the
30
+ * parent's variable, and adding it is what turns the entry into this workspace's own.
31
+ */
13
32
  findSlugsNotAdded(): Promise<Array<string>>;
14
33
  protected getResourcePath(_resource: Variable): string;
15
34
  protected removeFiles(variable: Variable): Promise<void>;
16
35
  protected resourceToMetadata(variable: Variable): ResourceMetadata;
36
+ /**
37
+ * A variable owned by this workspace goes to the committed variables file; one inherited
38
+ * from a parent workspace goes to the gitignored inherited file, so a local run can use it
39
+ * without the child workspace's git history carrying a copy of its parent's values.
40
+ */
17
41
  protected saveFiles(variable: Variable): Promise<void>;
18
42
  metadataFromFiles(slug: string, opts: AddResourceOptions): Promise<ResourceMetadata>;
19
43
  private readVariablesMeta;