@factorialco/fcode-cli 2.0.0 → 2.1.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 (36) hide show
  1. package/README.md +121 -1
  2. package/dist/commands/setup-debug.js +2 -2
  3. package/dist/commands/team/aliases/delete.d.ts +9 -0
  4. package/dist/commands/team/aliases/delete.js +28 -0
  5. package/dist/commands/team/aliases/list.d.ts +6 -0
  6. package/dist/commands/team/aliases/list.js +23 -0
  7. package/dist/commands/team/aliases/set.d.ts +10 -0
  8. package/dist/commands/team/aliases/set.js +25 -0
  9. package/dist/commands/team/pull.js +8 -1
  10. package/dist/commands/team/push.js +3 -0
  11. package/dist/commands/team/versions/create.d.ts +13 -0
  12. package/dist/commands/team/versions/create.js +42 -0
  13. package/dist/commands/team/versions/delete.d.ts +12 -0
  14. package/dist/commands/team/versions/delete.js +32 -0
  15. package/dist/commands/team/versions/list.d.ts +6 -0
  16. package/dist/commands/team/versions/list.js +25 -0
  17. package/dist/commands/variables/status.js +11 -0
  18. package/dist/config/settings.d.ts +1 -0
  19. package/dist/config/settings.js +6 -0
  20. package/dist/core/types.d.ts +37 -1
  21. package/dist/core/types.js +2 -1
  22. package/dist/env.d.ts +2 -2
  23. package/dist/env.js +1 -1
  24. package/dist/repositories/team.d.ts +7 -1
  25. package/dist/repositories/team.js +35 -1
  26. package/dist/repositories/variable.d.ts +24 -0
  27. package/dist/repositories/variable.js +70 -22
  28. package/dist/runtime/executor.js +2 -2
  29. package/dist/runtime/executor.py +2 -2
  30. package/dist/runtime/libs/python/fcode/__init__.py +2 -2
  31. package/dist/services/fcode-graphql-client.d.ts +10 -1
  32. package/dist/services/fcode-graphql-client.js +171 -23
  33. package/dist/utils/gitignore.d.ts +2 -0
  34. package/dist/utils/gitignore.js +6 -0
  35. package/oclif.manifest.json +350 -159
  36. package/package.json +10 -2
@@ -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));
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));
@@ -45,6 +53,25 @@ class TeamRepository {
45
53
  }
46
54
  return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig, this.parseWebhookAuth(raw.webhookAuth));
47
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));
74
+ }
48
75
  async resolveSlugToId(processSlug) {
49
76
  const { processes } = await this.workspaceConfig.getActiveTeam();
50
77
  return processes[processSlug]?.id ?? processSlug;
@@ -84,6 +111,13 @@ class TeamRepository {
84
111
  if (team.webhookAuth !== undefined) {
85
112
  file.webhookAuth = team.webhookAuth;
86
113
  }
114
+ // Pull-only global versioning fields, appended after the pushable settings.
115
+ if (team.versions !== undefined) {
116
+ file.versions = team.versions;
117
+ }
118
+ if (team.aliases !== undefined) {
119
+ file.aliases = team.aliases;
120
+ }
87
121
  return file;
88
122
  }
89
123
  }
@@ -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;
@@ -20,6 +20,7 @@ class VariableRepository extends resource_1.default {
20
20
  });
21
21
  this.filename = null;
22
22
  this.localFilename = null;
23
+ this.inheritedFilename = null;
23
24
  this.filenameInitPromise = null;
24
25
  }
25
26
  async ensureFilenamesInitialized() {
@@ -30,40 +31,67 @@ class VariableRepository extends resource_1.default {
30
31
  const globalConfig = new global_1.default();
31
32
  this.filename = await globalConfig.getSetting(settings_1.SettingKeys.VARIABLES_FILE_NAME);
32
33
  this.localFilename = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
34
+ this.inheritedFilename = await globalConfig.getSetting(settings_1.SettingKeys.INHERITED_VARIABLES_FILE_NAME);
33
35
  })();
34
36
  return this.filenameInitPromise;
35
37
  }
36
38
  async findAll() {
37
39
  await this.ensureFilenamesInitialized();
38
- const rawVariables = this.readVariablesFromFile();
39
- const localOverrideKeys = new Set(Object.keys(this.readVariablesFromFile(true)));
40
+ const rawVariables = this.readVariablesFromFile('own');
41
+ const inheritedRawVariables = this.readVariablesFromFile('inherited');
42
+ const localOverrideKeys = new Set(Object.keys(this.readVariablesFromFile('local')));
40
43
  const meta = this.readVariablesMeta();
41
44
  const variables = [];
42
45
  const { variables: localVariables } = await this.workspaceConfig.getActiveTeam();
43
46
  for (const localVariableKey of Object.keys(localVariables)) {
44
47
  const localVariable = localVariables[localVariableKey];
45
48
  const isSensitive = meta[localVariable.key]?.isSensitive ?? false;
46
- const variable = types_1.Variable.validate(new types_1.Variable(localVariable.id, localVariable.key, rawVariables[localVariable.key], isSensitive));
49
+ // Writing an inherited key into variables.env is how you override it, so from that
50
+ // moment the variable is this workspace's: it drops the parent's id and inherited
51
+ // marker, which is what makes `push` create it here instead of skipping it.
52
+ const overridesInherited = this.overridesInherited(localVariable, rawVariables);
53
+ const resolvingTeamSlug = overridesInherited ? undefined : localVariable.resolvingTeamSlug;
54
+ const id = overridesInherited ? undefined : localVariable.id;
55
+ const rawValue = resolvingTeamSlug ? inheritedRawVariables[localVariable.key] : rawVariables[localVariable.key];
56
+ const variable = types_1.Variable.validate(new types_1.Variable(id, localVariable.key, rawValue, isSensitive, resolvingTeamSlug));
47
57
  variable.overriddenLocally = localOverrideKeys.has(localVariable.key);
48
58
  variables.push(variable);
49
59
  }
50
60
  return variables;
51
61
  }
62
+ /**
63
+ * Values as a local run sees them. Precedence, lowest first: variables inherited from
64
+ * parent workspaces, this workspace's own variables, and the local overrides file — the
65
+ * same order the platform applies, where a workspace's own value wins over an inherited one.
66
+ * An empty value is not an override but an unset one: the platform rejects blank values, and
67
+ * a variable emptied in variables.env is a removal (see `mapResourceStatus`), so an empty
68
+ * entry falls through to the layer below rather than blanking it.
69
+ */
52
70
  async findAllForExecution() {
53
71
  await this.ensureFilenamesInitialized();
54
- const rawVariables = this.readVariablesFromFile();
55
- const localRawVariables = this.readVariablesFromFile(true);
72
+ const rawVariables = this.readVariablesFromFile('own');
73
+ const inheritedRawVariables = this.readVariablesFromFile('inherited');
74
+ const localRawVariables = this.readVariablesFromFile('local');
56
75
  const meta = this.readVariablesMeta();
57
76
  const variables = [];
58
77
  const { variables: localVariables } = await this.workspaceConfig.getActiveTeam();
59
78
  for (const localVariableKey of Object.keys(localVariables)) {
60
79
  const localVariable = localVariables[localVariableKey];
61
- const value = localRawVariables[localVariable.key] || rawVariables[localVariable.key];
62
- const isSensitive = meta[localVariable.key]?.isSensitive ?? false;
63
- variables.push(types_1.Variable.validate(new types_1.Variable(localVariable.id, localVariable.key, value, isSensitive)));
80
+ const key = localVariable.key;
81
+ const isSensitive = meta[key]?.isSensitive ?? false;
82
+ const value = localRawVariables[key] || rawVariables[key] || inheritedRawVariables[key];
83
+ const resolvingTeamSlug = this.overridesInherited(localVariable, rawVariables) ? undefined : localVariable.resolvingTeamSlug;
84
+ variables.push(types_1.Variable.validate(new types_1.Variable(localVariable.id, key, value, isSensitive, resolvingTeamSlug)));
64
85
  }
65
86
  return variables;
66
87
  }
88
+ /**
89
+ * An inherited key the workspace has given a value of its own, overriding it. An empty entry
90
+ * does not count: the platform has no empty values, so it reads as "not set" here too.
91
+ */
92
+ overridesInherited(metadata, ownRawVariables) {
93
+ return Boolean(metadata.resolvingTeamSlug) && Boolean(ownRawVariables[metadata.key]);
94
+ }
67
95
  async find(slug) {
68
96
  let variables = await this.findAll();
69
97
  variables = variables.filter((v) => v.key === slug);
@@ -72,12 +100,19 @@ class VariableRepository extends resource_1.default {
72
100
  }
73
101
  throw new errors_1.LocalVariableNotFound(new Error('Variable not found on team metadata'), slug);
74
102
  }
103
+ /**
104
+ * Keys declared in the workspace's own variables file that its metadata does not claim yet.
105
+ * A key the workspace inherits counts as one of those: the metadata still points at the
106
+ * parent's variable, and adding it is what turns the entry into this workspace's own.
107
+ */
75
108
  async findSlugsNotAdded() {
76
109
  await this.ensureFilenamesInitialized();
110
+ const ownRawVariables = this.readVariablesFromFile('own');
77
111
  const { variables: localVariables } = await this.workspaceConfig.getActiveTeam();
78
112
  const slugsNotAdded = [];
79
- for (const [key, _value] of Object.entries(this.readVariablesFromFile())) {
80
- if (!localVariables[key]) {
113
+ for (const key of Object.keys(ownRawVariables)) {
114
+ const metadata = localVariables[key];
115
+ if (!metadata || this.overridesInherited(metadata, ownRawVariables)) {
81
116
  slugsNotAdded.push(key);
82
117
  }
83
118
  }
@@ -88,9 +123,10 @@ class VariableRepository extends resource_1.default {
88
123
  }
89
124
  async removeFiles(variable) {
90
125
  await this.ensureFilenamesInitialized();
91
- let rawVariables = this.readVariablesFromFile();
126
+ const file = variable.resolvingTeamSlug ? 'inherited' : 'own';
127
+ const rawVariables = this.readVariablesFromFile(file);
92
128
  delete rawVariables[variable.key];
93
- this.writeRawVariables(rawVariables);
129
+ this.writeRawVariables(rawVariables, file);
94
130
  const meta = this.readVariablesMeta();
95
131
  delete meta[variable.key];
96
132
  this.writeVariablesMeta(meta);
@@ -100,13 +136,20 @@ class VariableRepository extends resource_1.default {
100
136
  id: variable.id,
101
137
  slug: variable.slug,
102
138
  key: variable.key,
139
+ ...(variable.resolvingTeamSlug && { resolvingTeamSlug: variable.resolvingTeamSlug }),
103
140
  };
104
141
  }
142
+ /**
143
+ * A variable owned by this workspace goes to the committed variables file; one inherited
144
+ * from a parent workspace goes to the gitignored inherited file, so a local run can use it
145
+ * without the child workspace's git history carrying a copy of its parent's values.
146
+ */
105
147
  async saveFiles(variable) {
106
148
  await this.ensureFilenamesInitialized();
107
- let rawVariables = this.readVariablesFromFile();
108
- rawVariables = Object.assign(rawVariables, { [variable.key]: this.valueToSave(variable) });
109
- this.writeRawVariables(rawVariables);
149
+ const file = variable.resolvingTeamSlug ? 'inherited' : 'own';
150
+ const rawVariables = this.readVariablesFromFile(file);
151
+ rawVariables[variable.key] = this.valueToSave(variable);
152
+ this.writeRawVariables(rawVariables, file);
110
153
  const meta = this.readVariablesMeta();
111
154
  meta[variable.key] = { isSensitive: variable.isSensitive };
112
155
  this.writeVariablesMeta(meta);
@@ -139,15 +182,20 @@ class VariableRepository extends resource_1.default {
139
182
  }, {});
140
183
  (0, file_utils_1.outputFileSyncIfChanged)(VARIABLES_META_FILENAME, JSON.stringify(sorted, null, 2));
141
184
  }
142
- filePath(useLocalVersion = false) {
143
- if (!this.filename || !this.localFilename) {
185
+ filePath(file = 'own') {
186
+ if (!this.filename || !this.localFilename || !this.inheritedFilename) {
144
187
  throw new Error('Filenames not initialized. Call ensureFilenamesInitialized() first.');
145
188
  }
146
- return path.join(useLocalVersion ? this.localFilename : this.filename);
189
+ const filenames = {
190
+ own: this.filename,
191
+ local: this.localFilename,
192
+ inherited: this.inheritedFilename,
193
+ };
194
+ return path.join(filenames[file]);
147
195
  }
148
- readVariablesFromFile(useLocalVersion = false) {
196
+ readVariablesFromFile(file = 'own') {
149
197
  try {
150
- const result = (0, dotenv_1.parse)(fs.readFileSync(this.filePath(useLocalVersion), 'utf8')) || {};
198
+ const result = (0, dotenv_1.parse)(fs.readFileSync(this.filePath(file), 'utf8')) || {};
151
199
  for (const [key, value] of Object.entries(result)) {
152
200
  result[key] = (0, env_utils_1.unescapeEnvValue)(value);
153
201
  }
@@ -163,12 +211,12 @@ class VariableRepository extends resource_1.default {
163
211
  }
164
212
  return variable.value;
165
213
  }
166
- writeRawVariables(rawVariables) {
214
+ writeRawVariables(rawVariables, file = 'own') {
167
215
  const envContent = Object.entries(rawVariables)
168
216
  .sort((a, b) => a[0].localeCompare(b[0]))
169
217
  .map(([key, value]) => `${key}=${(0, env_utils_1.escapeEnvValue)(value)}`)
170
218
  .join('\n');
171
- (0, file_utils_1.outputFileSyncIfChanged)(this.filePath(), envContent);
219
+ (0, file_utils_1.outputFileSyncIfChanged)(this.filePath(file), envContent);
172
220
  }
173
221
  }
174
222
  exports.default = VariableRepository;
@@ -3,7 +3,7 @@ const path = require('node:path')
3
3
  const { execSync } = require('node:child_process')
4
4
  const os = require('node:os')
5
5
 
6
- const YC_COMMAND = process.env.YC_COMMAND ?? "fcode"
6
+ const FC_COMMAND = process.env.FC_COMMAND ?? "fcode"
7
7
 
8
8
  const file = process.argv[2]
9
9
  if (!fs.existsSync(file)) {
@@ -36,7 +36,7 @@ let dependenciesPath = ''
36
36
  try {
37
37
  version = getVersion(relativePath)
38
38
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fcode-'))
39
- const command = [YC_COMMAND, 'pre-debug', absolutePath, '-t', tempDir]
39
+ const command = [FC_COMMAND, 'pre-debug', absolutePath, '-t', tempDir]
40
40
  if (version) {
41
41
  command.push('-v')
42
42
  command.push(version)
@@ -4,7 +4,7 @@ import subprocess
4
4
  import tempfile
5
5
 
6
6
 
7
- YC_COMMAND = os.environ.get("YC_COMMAND", "fcode")
7
+ FC_COMMAND = os.environ.get("FC_COMMAND", "fcode")
8
8
  file = sys.argv[1]
9
9
  if not os.path.exists(file):
10
10
  print("File not found: " + file)
@@ -31,7 +31,7 @@ main_file_path = ""
31
31
  try:
32
32
  version = get_version(file)
33
33
  tempDir = tempfile.TemporaryDirectory()
34
- command = [YC_COMMAND, "pre-debug", file, "-t", tempDir.name]
34
+ command = [FC_COMMAND, "pre-debug", file, "-t", tempDir.name]
35
35
  if version != None:
36
36
  command.append("-v")
37
37
  command.append(version)
@@ -3,7 +3,7 @@ import json
3
3
  import subprocess
4
4
  import logging
5
5
 
6
- YC_COMMAND = os.environ.get("YC_COMMAND", "fcode")
6
+ FC_COMMAND = os.environ.get("FC_COMMAND", "fcode")
7
7
 
8
8
  metadata = {
9
9
  "execution": {"process": {"name": "", "id": ""}},
@@ -24,7 +24,7 @@ else:
24
24
  os.environ.get("FCODE_LOG_LEVEL", "") == "DEBUG" and print(
25
25
  "[FCODE LIBRARY] Reading metadata from process-metadata command"
26
26
  )
27
- command = [YC_COMMAND, "process-metadata"]
27
+ command = [FC_COMMAND, "process-metadata"]
28
28
  if process_slug:
29
29
  command.append(process_slug)
30
30
  if process_slug and process_version:
@@ -1,5 +1,5 @@
1
1
  import { GraphQLClient } from 'graphql-request';
2
- import { Process, ProgrammingLanguage, ProgrammingLanguageManifest, RawTeamSettings, ScriptLibrary, TeamUpdateInput, Variable, VersionedProcess, VersionedScriptLibrary, ServiceAccount } from '../core/types';
2
+ import { Process, ProgrammingLanguage, ProgrammingLanguageManifest, RawTeamSettings, ScriptLibrary, TeamUpdateInput, TeamVersion, TeamVersionAlias, Variable, VersionedProcess, VersionedScriptLibrary, ServiceAccount } from '../core/types';
3
3
  export type FcodeGraphQLClientOptions = {
4
4
  url: string;
5
5
  accessToken: string;
@@ -44,6 +44,15 @@ declare class FcodeGraphQLClient {
44
44
  * undefined.
45
45
  */
46
46
  mapTeamSettings(team: any): RawTeamSettings;
47
+ mapTeamVersion(teamVersion: any): TeamVersion;
48
+ mapTeamVersionAlias(alias: any): TeamVersionAlias;
49
+ allTeamVersions(): Promise<Array<TeamVersion>>;
50
+ createTeamVersion(tag: string, comment?: string): Promise<TeamVersion>;
51
+ deleteTeamVersion(tag: string): Promise<TeamVersion>;
52
+ allTeamVersionAliases(): Promise<Array<TeamVersionAlias>>;
53
+ /** The API upserts: an existing alias with the same name is re-pointed to the tag. */
54
+ createTeamVersionAlias(name: string, tag: string): Promise<TeamVersionAlias>;
55
+ deleteTeamVersionAlias(id: string): Promise<TeamVersionAlias>;
47
56
  team(): Promise<RawTeamSettings>;
48
57
  pushTeam(update: TeamUpdateInput): Promise<RawTeamSettings>;
49
58
  mapProgrammingLanguageManifest(programmingLanguageManifest: any): ProgrammingLanguageManifest;
@@ -3,6 +3,31 @@ 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 _ = require("lodash");
6
+ // Shared team version selections so every query/mutation returns the same shape.
7
+ const TEAM_VERSION_FIELDS = `
8
+ id
9
+ tag
10
+ comment
11
+ createdAt
12
+ createdBy {
13
+ id
14
+ name
15
+ }
16
+ manifest {
17
+ resourceType
18
+ resourceId
19
+ slug
20
+ outcome
21
+ error
22
+ }
23
+ `;
24
+ const TEAM_VERSION_ALIAS_FIELDS = `
25
+ id
26
+ name
27
+ tag
28
+ createdAt
29
+ updatedAt
30
+ `;
6
31
  // Shared process selection so pull queries and push mutations return the same
7
32
  // fields, keeping mapProcess (and therefore contentHash) consistent.
8
33
  const PROCESS_FIELDS = `
@@ -236,33 +261,46 @@ class FcodeGraphQLClient {
236
261
  return Promise.reject(this.cleanError(error));
237
262
  });
238
263
  }
239
- allVariables() {
240
- return this.client
241
- .request((0, graphql_request_1.gql) `
242
- query teamVariables($first: Int!, $after: String) {
243
- teamVariables(first: $first, after: $after) {
244
- edges {
245
- node {
246
- id
247
- key
248
- value
249
- isSensitive
264
+ // Pages through every variable: a workspace lists its own plus the ones inherited from
265
+ // its parent workspaces, so a single page is not enough to be sure nothing is missed.
266
+ async allVariables() {
267
+ const pageSize = 200;
268
+ const variables = [];
269
+ let after = null;
270
+ try {
271
+ do {
272
+ const { teamVariables: { edges, pageInfo }, } = await this.client.request((0, graphql_request_1.gql) `
273
+ query teamVariables($first: Int!, $after: String) {
274
+ teamVariables(first: $first, after: $after) {
275
+ edges {
276
+ node {
277
+ id
278
+ key
279
+ value
280
+ isSensitive
281
+ resolvingTeamSlug
282
+ }
283
+ }
284
+ pageInfo {
285
+ hasNextPage
286
+ endCursor
250
287
  }
251
- }
252
- pageInfo {
253
- hasNextPage
254
- endCursor
255
288
  }
256
289
  }
257
- }
258
- `, {
259
- after: null,
260
- first: 200,
261
- })
262
- .then(({ teamVariables: { edges } }) => edges.map(({ node: { id, isSensitive, key, value } }) => new types_1.Variable(id, key, value, isSensitive)))
263
- .catch((error) => {
290
+ `, {
291
+ after,
292
+ first: pageSize,
293
+ });
294
+ for (const { node: { id, isSensitive, key, value, resolvingTeamSlug }, } of edges) {
295
+ variables.push(new types_1.Variable(id, key, value, isSensitive, this.inheritedTeam(resolvingTeamSlug)));
296
+ }
297
+ after = pageInfo?.hasNextPage ? pageInfo.endCursor : null;
298
+ } while (after);
299
+ }
300
+ catch (error) {
264
301
  return Promise.reject(this.cleanError(error));
265
- });
302
+ }
303
+ return variables;
266
304
  }
267
305
  toProcessInput(process, includeCreateOnly) {
268
306
  const settings = {
@@ -603,6 +641,116 @@ class FcodeGraphQLClient {
603
641
  }
604
642
  return settings;
605
643
  }
644
+ mapTeamVersion(teamVersion) {
645
+ return {
646
+ id: teamVersion.id,
647
+ tag: teamVersion.tag,
648
+ comment: teamVersion.comment ?? null,
649
+ manifest: (teamVersion.manifest ?? []).map((entry) => ({
650
+ resourceType: entry.resourceType,
651
+ resourceId: entry.resourceId,
652
+ slug: entry.slug,
653
+ outcome: entry.outcome,
654
+ error: entry.error ?? null,
655
+ })),
656
+ createdAt: teamVersion.createdAt,
657
+ createdBy: teamVersion.createdBy ?? null,
658
+ };
659
+ }
660
+ mapTeamVersionAlias(alias) {
661
+ return {
662
+ id: alias.id,
663
+ name: alias.name,
664
+ tag: alias.tag,
665
+ createdAt: alias.createdAt,
666
+ updatedAt: alias.updatedAt ?? null,
667
+ };
668
+ }
669
+ allTeamVersions() {
670
+ return this.client
671
+ .request((0, graphql_request_1.gql) `
672
+ query allTeamVersions {
673
+ allTeamVersions {
674
+ ${TEAM_VERSION_FIELDS}
675
+ }
676
+ }
677
+ `)
678
+ .then(({ allTeamVersions }) => allTeamVersions.map((teamVersion) => this.mapTeamVersion(teamVersion)))
679
+ .catch((error) => {
680
+ return Promise.reject(this.cleanError(error));
681
+ });
682
+ }
683
+ createTeamVersion(tag, comment) {
684
+ return this.client
685
+ .request((0, graphql_request_1.gql) `
686
+ mutation createTeamVersion($tag: String!, $comment: String) {
687
+ createTeamVersion(tag: $tag, comment: $comment) {
688
+ ${TEAM_VERSION_FIELDS}
689
+ }
690
+ }
691
+ `, { tag, comment: comment ?? null })
692
+ .then(({ createTeamVersion }) => this.mapTeamVersion(createTeamVersion))
693
+ .catch((error) => {
694
+ return Promise.reject(this.cleanError(error));
695
+ });
696
+ }
697
+ deleteTeamVersion(tag) {
698
+ return this.client
699
+ .request((0, graphql_request_1.gql) `
700
+ mutation deleteTeamVersion($tag: String!) {
701
+ deleteTeamVersion(tag: $tag) {
702
+ ${TEAM_VERSION_FIELDS}
703
+ }
704
+ }
705
+ `, { tag })
706
+ .then(({ deleteTeamVersion }) => this.mapTeamVersion(deleteTeamVersion))
707
+ .catch((error) => {
708
+ return Promise.reject(this.cleanError(error));
709
+ });
710
+ }
711
+ allTeamVersionAliases() {
712
+ return this.client
713
+ .request((0, graphql_request_1.gql) `
714
+ query allTeamVersionAliases {
715
+ allTeamVersionAliases {
716
+ ${TEAM_VERSION_ALIAS_FIELDS}
717
+ }
718
+ }
719
+ `)
720
+ .then(({ allTeamVersionAliases }) => allTeamVersionAliases.map((alias) => this.mapTeamVersionAlias(alias)))
721
+ .catch((error) => {
722
+ return Promise.reject(this.cleanError(error));
723
+ });
724
+ }
725
+ /** The API upserts: an existing alias with the same name is re-pointed to the tag. */
726
+ createTeamVersionAlias(name, tag) {
727
+ return this.client
728
+ .request((0, graphql_request_1.gql) `
729
+ mutation createTeamVersionAlias($input: TeamVersionAliasInput!) {
730
+ createTeamVersionAlias(input: $input) {
731
+ ${TEAM_VERSION_ALIAS_FIELDS}
732
+ }
733
+ }
734
+ `, { input: { name, tag } })
735
+ .then(({ createTeamVersionAlias }) => this.mapTeamVersionAlias(createTeamVersionAlias))
736
+ .catch((error) => {
737
+ return Promise.reject(this.cleanError(error));
738
+ });
739
+ }
740
+ deleteTeamVersionAlias(id) {
741
+ return this.client
742
+ .request((0, graphql_request_1.gql) `
743
+ mutation deleteTeamVersionAlias($id: ID!) {
744
+ deleteTeamVersionAlias(id: $id) {
745
+ ${TEAM_VERSION_ALIAS_FIELDS}
746
+ }
747
+ }
748
+ `, { id })
749
+ .then(({ deleteTeamVersionAlias }) => this.mapTeamVersionAlias(deleteTeamVersionAlias))
750
+ .catch((error) => {
751
+ return Promise.reject(this.cleanError(error));
752
+ });
753
+ }
606
754
  team() {
607
755
  return this.client
608
756
  .request((0, graphql_request_1.gql) `
@@ -13,5 +13,7 @@ export declare function renderGitignoreWithInherited(existing: string, inherited
13
13
  * Refreshes the workspace .gitignore so inherited processes/modules (owned by a
14
14
  * parent team and pulled read-only) are not tracked by git. Regenerated from the
15
15
  * current workspace metadata, so resources that stop being inherited drop out.
16
+ * Inherited variable values live in one file, which is ignored as soon as the
17
+ * workspace inherits any variable.
16
18
  */
17
19
  export declare function syncInheritedGitignore(workspaceDir?: string): Promise<void>;
@@ -6,6 +6,8 @@ exports.syncInheritedGitignore = syncInheritedGitignore;
6
6
  const path = require("node:path");
7
7
  const fs = require("fs-extra");
8
8
  const workspace_1 = require("../config/workspace");
9
+ const global_1 = require("../config/global");
10
+ const settings_1 = require("../config/settings");
9
11
  const filenamify_1 = require("./filenamify");
10
12
  const file_utils_1 = require("./file-utils");
11
13
  exports.INHERITED_BLOCK_START = '# BEGIN fcode inherited resources (auto-generated, do not edit)';
@@ -43,9 +45,12 @@ function stripManagedBlock(content) {
43
45
  * Refreshes the workspace .gitignore so inherited processes/modules (owned by a
44
46
  * parent team and pulled read-only) are not tracked by git. Regenerated from the
45
47
  * current workspace metadata, so resources that stop being inherited drop out.
48
+ * Inherited variable values live in one file, which is ignored as soon as the
49
+ * workspace inherits any variable.
46
50
  */
47
51
  async function syncInheritedGitignore(workspaceDir = process.cwd()) {
48
52
  const team = await new workspace_1.default(workspaceDir).getActiveTeam();
53
+ const inheritedVariablesFileName = await new global_1.default().getSetting(settings_1.SettingKeys.INHERITED_VARIABLES_FILE_NAME);
49
54
  const inheritedPaths = [
50
55
  ...Object.values(team.processes)
51
56
  .filter((process) => process.resolvingTeamSlug)
@@ -53,6 +58,7 @@ async function syncInheritedGitignore(workspaceDir = process.cwd()) {
53
58
  ...Object.values(team.modules)
54
59
  .filter((module) => module.resolvingTeamSlug)
55
60
  .map((module) => `modules/${(0, filenamify_1.default)(module.name)}/`),
61
+ ...(Object.values(team.variables ?? {}).some((variable) => variable.resolvingTeamSlug) ? [inheritedVariablesFileName] : []),
56
62
  ];
57
63
  const gitignorePath = path.join(workspaceDir, '.gitignore');
58
64
  const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';