@factorialco/fcode-cli 2.4.0 → 3.0.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 (114) hide show
  1. package/README.md +331 -118
  2. package/dist/commands/clone.d.ts +0 -12
  3. package/dist/commands/clone.js +22 -135
  4. package/dist/commands/{settings → config}/get.d.ts +1 -1
  5. package/dist/commands/{settings → config}/get.js +7 -7
  6. package/dist/commands/{settings → config}/remove.d.ts +1 -1
  7. package/dist/commands/{settings → config}/remove.js +6 -6
  8. package/dist/commands/{settings → config}/set.d.ts +1 -1
  9. package/dist/commands/{settings → config}/set.js +8 -8
  10. package/dist/commands/dependencies/diff.d.ts +9 -0
  11. package/dist/commands/dependencies/diff.js +15 -0
  12. package/dist/commands/dependencies/pull.d.ts +9 -0
  13. package/dist/commands/dependencies/pull.js +44 -1
  14. package/dist/commands/diff.d.ts +12 -0
  15. package/dist/commands/diff.js +48 -0
  16. package/dist/commands/http.d.ts +1 -1
  17. package/dist/commands/http.js +7 -7
  18. package/dist/commands/i18n/diff.d.ts +9 -0
  19. package/dist/commands/i18n/diff.js +15 -0
  20. package/dist/commands/i18n/status.d.ts +3 -0
  21. package/dist/commands/i18n/status.js +1 -0
  22. package/dist/commands/modules/diff.d.ts +9 -0
  23. package/dist/commands/modules/diff.js +15 -0
  24. package/dist/commands/modules/status.d.ts +3 -0
  25. package/dist/commands/modules/status.js +1 -0
  26. package/dist/commands/processes/diff.d.ts +9 -0
  27. package/dist/commands/processes/diff.js +15 -0
  28. package/dist/commands/processes/status.d.ts +3 -0
  29. package/dist/commands/processes/status.js +1 -0
  30. package/dist/commands/pull.js +2 -2
  31. package/dist/commands/push.js +2 -2
  32. package/dist/commands/{team → settings}/aliases/delete.d.ts +1 -1
  33. package/dist/commands/{team → settings}/aliases/delete.js +6 -6
  34. package/dist/commands/{team → settings}/aliases/list.d.ts +1 -1
  35. package/dist/commands/{team → settings}/aliases/list.js +8 -8
  36. package/dist/commands/{team → settings}/aliases/set.d.ts +1 -1
  37. package/dist/commands/{team → settings}/aliases/set.js +8 -8
  38. package/dist/commands/settings/diff.d.ts +8 -0
  39. package/dist/commands/settings/diff.js +39 -0
  40. package/dist/commands/settings/pull.d.ts +11 -0
  41. package/dist/commands/settings/pull.js +41 -0
  42. package/dist/commands/{team → settings}/push.d.ts +4 -4
  43. package/dist/commands/{team → settings}/push.js +39 -39
  44. package/dist/commands/settings/status.d.ts +8 -0
  45. package/dist/commands/settings/status.js +46 -0
  46. package/dist/commands/{team → settings}/versions/create.d.ts +1 -1
  47. package/dist/commands/{team → settings}/versions/create.js +13 -10
  48. package/dist/commands/{team → settings}/versions/delete.d.ts +1 -1
  49. package/dist/commands/{team → settings}/versions/delete.js +10 -10
  50. package/dist/commands/{team → settings}/versions/list.d.ts +1 -1
  51. package/dist/commands/{team → settings}/versions/list.js +8 -8
  52. package/dist/commands/status.d.ts +3 -0
  53. package/dist/commands/status.js +10 -7
  54. package/dist/commands/team/clone.d.ts +14 -0
  55. package/dist/commands/team/clone.js +91 -0
  56. package/dist/commands/team/pull.d.ts +3 -7
  57. package/dist/commands/team/pull.js +25 -30
  58. package/dist/commands/team/status.d.ts +3 -4
  59. package/dist/commands/team/status.js +34 -33
  60. package/dist/commands/variables/diff.d.ts +9 -0
  61. package/dist/commands/variables/diff.js +15 -0
  62. package/dist/commands/variables/status.d.ts +3 -0
  63. package/dist/commands/variables/status.js +1 -0
  64. package/dist/config/dev-team.d.ts +48 -0
  65. package/dist/config/dev-team.js +62 -0
  66. package/dist/config/workspace.d.ts +2 -2
  67. package/dist/config/workspace.js +4 -2
  68. package/dist/core/commands/base-command.js +5 -0
  69. package/dist/core/commands/dependency-command.d.ts +2 -1
  70. package/dist/core/commands/dependency-command.js +3 -0
  71. package/dist/core/commands/i18n-command.js +2 -0
  72. package/dist/core/commands/modules-command.js +2 -0
  73. package/dist/core/commands/processes-command.js +2 -0
  74. package/dist/core/commands/resource-command.d.ts +26 -1
  75. package/dist/core/commands/resource-command.js +100 -3
  76. package/dist/core/commands/variables-command.d.ts +7 -0
  77. package/dist/core/commands/variables-command.js +13 -0
  78. package/dist/core/executors/javascript-execution-task.js +25 -3
  79. package/dist/core/types.d.ts +30 -7
  80. package/dist/core/types.js +38 -10
  81. package/dist/repositories/process.d.ts +8 -2
  82. package/dist/repositories/process.js +28 -18
  83. package/dist/repositories/programming-language-manifest.d.ts +15 -1
  84. package/dist/repositories/programming-language-manifest.js +45 -1
  85. package/dist/repositories/settings.d.ts +52 -0
  86. package/dist/repositories/settings.js +214 -0
  87. package/dist/runtime/libs/python/fcode/context.py +29 -2
  88. package/dist/services/dashboard-client.d.ts +31 -0
  89. package/dist/services/dashboard-client.js +99 -0
  90. package/dist/services/dev-team-service.d.ts +41 -0
  91. package/dist/services/dev-team-service.js +162 -0
  92. package/dist/services/fcode-graphql-client.d.ts +16 -2
  93. package/dist/services/fcode-graphql-client.js +56 -7
  94. package/dist/services/locale-resolver.d.ts +16 -5
  95. package/dist/services/locale-resolver.js +60 -12
  96. package/dist/services/resource-differ.d.ts +60 -0
  97. package/dist/services/resource-differ.js +135 -0
  98. package/dist/services/skills-installer.d.ts +16 -0
  99. package/dist/services/skills-installer.js +77 -0
  100. package/dist/services/team-versioning.d.ts +4 -4
  101. package/dist/services/team-versioning.js +4 -4
  102. package/dist/services/webhook-auth.js +1 -1
  103. package/dist/services/workspace-cloner.d.ts +31 -0
  104. package/dist/services/workspace-cloner.js +95 -0
  105. package/dist/utils/dev-slug.d.ts +12 -0
  106. package/dist/utils/dev-slug.js +37 -0
  107. package/dist/utils/diff.d.ts +19 -0
  108. package/dist/utils/diff.js +58 -0
  109. package/dist/utils/slugify.d.ts +17 -0
  110. package/dist/utils/slugify.js +41 -0
  111. package/oclif.manifest.json +428 -116
  112. package/package.json +12 -7
  113. package/dist/repositories/team.d.ts +0 -41
  114. package/dist/repositories/team.js +0 -170
@@ -1,12 +1,22 @@
1
- import type { AddResourceOptions, MergedResource, Resource } from '../types';
1
+ import { ResourceStatus, type AddResourceOptions, type MergedResource, type Resource } from '../types';
2
2
  import ResourceRepositoryBase from '../../repositories/resource';
3
3
  import ResourceMerger from '../resource-merger';
4
4
  import ResourcePusher from '../resource-pusher';
5
5
  import ApiClientCommand from './api-client-command';
6
+ import { PseudoFile } from '../../utils/diff';
7
+ import { DiffSection, ResourcePair } from '../../services/resource-differ';
8
+ /** A status meaning the two sides agree, so there is nothing to pull, push or diff. */
9
+ export declare const isUnchangedStatus: (status: ResourceStatus) => boolean;
10
+ /**
11
+ * An inherited resource the parent workspace owns and the cloud agrees on is noise in `status`:
12
+ * there is nothing to pull or push. It comes back as soon as it differs — or with --showInherited.
13
+ */
14
+ export declare const isUnchangedInherited: (resource: MergedResource) => boolean;
6
15
  export default abstract class ResourceCommand<R extends Resource> extends ApiClientCommand {
7
16
  _resourceMerger: ResourceMerger<R>;
8
17
  _resourcePusher: ResourcePusher<R>;
9
18
  _resourceQuery: () => Promise<Array<R>>;
19
+ _diffFilesFor: (pair: ResourcePair<R>) => Array<PseudoFile>;
10
20
  _resourceRepository: ResourceRepositoryBase<R>;
11
21
  protected resourceSlug: string;
12
22
  protected filterResource: (slug: string) => boolean;
@@ -20,5 +30,20 @@ export default abstract class ResourceCommand<R extends Resource> extends ApiCli
20
30
  removeResource(slug: string): Promise<void>;
21
31
  pushResources(): Promise<void>;
22
32
  resourcesLog(): Promise<void>;
33
+ /**
34
+ * One heading per resource, so a patch is always attributable to a slug and its status. Variables
35
+ * override this: they share a single .env file, and one patch over it reads better than one per key.
36
+ *
37
+ * `allPairs` carries every resource, including the unchanged ones, for a renderer that needs the
38
+ * whole set to place a change in context; `diffable` is only what a status says has changed.
39
+ */
40
+ protected diffSections(diffable: Array<ResourcePair<R>>, allPairs: Array<ResourcePair<R>>): Array<DiffSection>;
41
+ /** Names the resource a patch belongs to, flagging an inherited one as the parent's to fix. */
42
+ protected diffLabel(pair: ResourcePair<R>): string;
43
+ /**
44
+ * Prints a unified diff of everything that differs between the workspace and the cloud, and
45
+ * reports whether anything did — which is what makes `fcode diff` exit non-zero.
46
+ */
47
+ resourcesDiff(): Promise<boolean>;
23
48
  resourcesTableLog(resources: Array<MergedResource>): Promise<void>;
24
49
  }
@@ -1,9 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isUnchangedInherited = exports.isUnchangedStatus = void 0;
3
4
  const color_1 = require("@oclif/color");
4
5
  const cli_1 = require("../../utils/cli");
6
+ const types_1 = require("../types");
5
7
  const api_client_command_1 = require("./api-client-command");
6
8
  const colorize_resource_1 = require("../../utils/colorize-resource");
9
+ const diff_1 = require("../../utils/diff");
10
+ const resource_differ_1 = require("../../services/resource-differ");
11
+ // A sensitive variable never compares equal — its content hash is empty — so SENSITIVE is the
12
+ // resting state of an inherited secret, not a change, and counts as unchanged here.
13
+ const UNCHANGED_STATUSES = new Set([types_1.ResourceStatus.UP_TO_DATE, types_1.ResourceStatus.SENSITIVE]);
14
+ /** A status meaning the two sides agree, so there is nothing to pull, push or diff. */
15
+ const isUnchangedStatus = (status) => UNCHANGED_STATUSES.has(status);
16
+ exports.isUnchangedStatus = isUnchangedStatus;
17
+ /**
18
+ * An inherited resource the parent workspace owns and the cloud agrees on is noise in `status`:
19
+ * there is nothing to pull or push. It comes back as soon as it differs — or with --showInherited.
20
+ */
21
+ const isUnchangedInherited = (resource) => Boolean(resource.resolvingTeamSlug) && (0, exports.isUnchangedStatus)(resource.status);
22
+ exports.isUnchangedInherited = isUnchangedInherited;
7
23
  class ResourceCommand extends api_client_command_1.default {
8
24
  constructor() {
9
25
  super(...arguments);
@@ -60,18 +76,99 @@ class ResourceCommand extends api_client_command_1.default {
60
76
  async resourcesLog() {
61
77
  cli_1.default.log(color_1.default.cyan(`\n${this._resourceRepository._repositoryName} status`));
62
78
  const mergedResources = await this._resourceMerger.mergeStatus(await this.getRemoteResources(), await this.getLocalResources());
63
- if (mergedResources.length > 0) {
64
- await this.resourcesTableLog(mergedResources);
79
+ const { flags: { showInherited }, } = await this.parse(this.ctor);
80
+ const visibleResources = showInherited ? mergedResources : mergedResources.filter((resource) => !(0, exports.isUnchangedInherited)(resource));
81
+ const hiddenCount = mergedResources.length - visibleResources.length;
82
+ if (visibleResources.length > 0) {
83
+ await this.resourcesTableLog(visibleResources);
65
84
  }
66
- else {
85
+ else if (mergedResources.length === 0) {
67
86
  cli_1.default.log(`No ${this._resourceRepository._repositoryName} found`);
68
87
  }
88
+ if (hiddenCount > 0) {
89
+ cli_1.default.log(color_1.default.cyan(`ℹ️ ${hiddenCount} inherited ${this._resourceRepository._repositoryName} up to date (hidden). Use --showInherited to list them.`));
90
+ }
69
91
  const slugsNotAdded = await this.findSlugsNotAdded();
70
92
  if (slugsNotAdded.length > 0) {
71
93
  const warningText = `\nℹ️ ${slugsNotAdded.length} ${this._resourceRepository._repositoryName} only existing in local: ${slugsNotAdded.join(', ')}\n Use command 'fcode ${this._resourceRepository._repositoryName}:add' to keep track of these resources`;
72
94
  cli_1.default.log(color_1.default.yellow(warningText));
73
95
  }
74
96
  }
97
+ /**
98
+ * One heading per resource, so a patch is always attributable to a slug and its status. Variables
99
+ * override this: they share a single .env file, and one patch over it reads better than one per key.
100
+ *
101
+ * `allPairs` carries every resource, including the unchanged ones, for a renderer that needs the
102
+ * whole set to place a change in context; `diffable` is only what a status says has changed.
103
+ */
104
+ diffSections(diffable, allPairs) {
105
+ return diffable.map((pair) => ({
106
+ label: this.diffLabel(pair),
107
+ files: this._diffFilesFor(pair),
108
+ }));
109
+ }
110
+ /** Names the resource a patch belongs to, flagging an inherited one as the parent's to fix. */
111
+ diffLabel(pair) {
112
+ const label = `${pair.status} ${this._resourceRepository._repositoryName}/${pair.slug}`;
113
+ const inheritedTeam = (0, resource_differ_1.inheritedFrom)(pair);
114
+ return inheritedTeam ? `${label} 🔗 inherited from '${inheritedTeam}' (read-only — run pull to update)` : label;
115
+ }
116
+ /**
117
+ * Prints a unified diff of everything that differs between the workspace and the cloud, and
118
+ * reports whether anything did — which is what makes `fcode diff` exit non-zero.
119
+ */
120
+ async resourcesDiff() {
121
+ cli_1.default.log(color_1.default.cyan(`\n${this._resourceRepository._repositoryName} diff`));
122
+ const pairs = await (0, resource_differ_1.pairResources)(await this.getRemoteResources(), await this.getLocalResources(), (slug) => this._resourceRepository.existsMetadata(slug));
123
+ const notes = [];
124
+ const diffable = [];
125
+ let hiddenInherited = 0;
126
+ let sensitivityChanged = false;
127
+ for (const pair of pairs) {
128
+ // Same rule `status` applies: an inherited resource the cloud agrees on has nothing to show,
129
+ // but one that drifted is exactly what the user needs — a parent moving it is never swallowed.
130
+ if ((0, resource_differ_1.inheritedFrom)(pair) && (0, exports.isUnchangedStatus)(pair.status)) {
131
+ hiddenInherited++;
132
+ continue;
133
+ }
134
+ // A sensitive value is unknowable on both sides, so there is nothing to diff; a sensitivity
135
+ // change is a real difference the patch cannot show, so it is reported instead.
136
+ if (pair.status === types_1.ResourceStatus.SENSITIVITY_CHANGED) {
137
+ sensitivityChanged = true;
138
+ notes.push(color_1.default.yellow(`${pair.status} ${pair.slug} — sensitivity differs between local and cloud (values are never shown)`));
139
+ continue;
140
+ }
141
+ // An up-to-date resource is skipped rather than rendered and found equal: its two sides can
142
+ // still serialize differently (key order in parametersSchema.json, say) where the content
143
+ // hash — which is what "up to date" means — says they are the same.
144
+ if ((0, exports.isUnchangedStatus)(pair.status)) {
145
+ continue;
146
+ }
147
+ diffable.push(pair);
148
+ }
149
+ const sections = this.diffSections(diffable, pairs)
150
+ .map((section) => ({ ...section, files: section.files.filter((file) => (0, diff_1.hasDifference)(file)) }))
151
+ .filter((section) => section.files.length > 0);
152
+ for (const section of sections) {
153
+ cli_1.default.log('');
154
+ cli_1.default.log((0, colorize_resource_1.colorizeByStatus)(section.label, section.label));
155
+ for (const file of section.files) {
156
+ cli_1.default.log((0, diff_1.colorizeDiff)((0, diff_1.renderUnifiedDiff)(file)));
157
+ }
158
+ }
159
+ for (const note of notes) {
160
+ cli_1.default.log(note);
161
+ }
162
+ if (sections.length === 0 && !sensitivityChanged) {
163
+ cli_1.default.log(color_1.default.green(`✅ ${this._resourceRepository._repositoryName} up to date`));
164
+ }
165
+ // Counted rather than listed: a workspace built on a parent inherits dozens of resources, and a
166
+ // line each would bury the diff it was run for. Only the ones with nothing to show are in here.
167
+ if (hiddenInherited > 0) {
168
+ cli_1.default.log(color_1.default.cyan(`ℹ️ ${hiddenInherited} inherited ${this._resourceRepository._repositoryName} up to date (nothing to diff).`));
169
+ }
170
+ return sections.length > 0 || sensitivityChanged;
171
+ }
75
172
  async resourcesTableLog(resources) {
76
173
  cli_1.default.table(resources, {
77
174
  name: {
@@ -1,5 +1,12 @@
1
1
  import { Variable } from '../types';
2
2
  import ResourceCommand from './resource-command';
3
+ import { DiffSection, ResourcePair } from '../../services/resource-differ';
3
4
  export default abstract class VariablesCommand extends ResourceCommand<Variable> {
4
5
  init(): Promise<void>;
6
+ /**
7
+ * Variables share their files, so they diff as one patch per file rather than one per key.
8
+ * Rendered from every variable, not just the changed ones, so hunks carry the surrounding keys
9
+ * and the line numbers of the real file.
10
+ */
11
+ protected diffSections(diffable: Array<ResourcePair<Variable>>, allPairs: Array<ResourcePair<Variable>>): Array<DiffSection>;
5
12
  }
@@ -4,6 +4,7 @@ const variable_1 = require("../../repositories/variable");
4
4
  const resource_merger_1 = require("../resource-merger");
5
5
  const resource_pusher_1 = require("../resource-pusher");
6
6
  const resource_command_1 = require("./resource-command");
7
+ const resource_differ_1 = require("../../services/resource-differ");
7
8
  class VariablesCommand extends resource_command_1.default {
8
9
  async init() {
9
10
  await super.init();
@@ -11,6 +12,18 @@ class VariablesCommand extends resource_command_1.default {
11
12
  this._resourceMerger = new resource_merger_1.default(this._resourceRepository);
12
13
  this._resourcePusher = new resource_pusher_1.default(this._resourceRepository, this._fcodeGraphQLClient);
13
14
  this._resourceQuery = () => this._fcodeGraphQLClient.allVariables.bind(this._fcodeGraphQLClient)();
15
+ this._diffFilesFor = (pair) => (0, resource_differ_1.variablesEnvDiffFiles)([pair]);
16
+ }
17
+ /**
18
+ * Variables share their files, so they diff as one patch per file rather than one per key.
19
+ * Rendered from every variable, not just the changed ones, so hunks carry the surrounding keys
20
+ * and the line numbers of the real file.
21
+ */
22
+ diffSections(diffable, allPairs) {
23
+ if (diffable.length === 0) {
24
+ return [];
25
+ }
26
+ return [{ label: resource_differ_1.VARIABLES_DIFF_PATH, files: (0, resource_differ_1.variablesEnvDiffFiles)(allPairs) }];
14
27
  }
15
28
  }
16
29
  exports.default = VariablesCommand;
@@ -596,7 +596,8 @@ const main = typeof _hooksModule['${fn}'] === 'function'
596
596
  // A workspace directory holds no published snapshots, so a pinned call resolves against
597
597
  // the files on disk — the same per-file fallback the cloud applies to a locale that was
598
598
  // never published at that tag. A locale option resolves against that locale's own files,
599
- // falling back key by key to the merged translations, exactly like the cloud executors.
599
+ // falling back key by key to the merged translations, exactly like the cloud executors,
600
+ // shortening a locale nothing matches the same way they do.
600
601
  // Own properties only and no array arguments, exactly like the cloud executors.
601
602
  i18n: Object.assign((key, args, options) => {
602
603
  const translations = ${JSON.stringify(metadata.i18n.translations, null, 2)};
@@ -604,8 +605,29 @@ const main = typeof _hooksModule['${fn}'] === 'function'
604
605
  const requested = options && typeof options === 'object' && options.locale !== undefined && options.locale !== null
605
606
  ? String(options.locale)
606
607
  : undefined;
607
- const localeMap = requested !== undefined && Object.hasOwn(byLocale, requested) ? byLocale[requested] : undefined;
608
- let template = localeMap && Object.hasOwn(localeMap, key) ? localeMap[key] : undefined;
608
+ // An identifier no locale matches is shortened one segment at a time ('en-gb' reads
609
+ // 'en'), by name first and ignoring case only as a last resort — the same chain the
610
+ // cloud resolves, so a local run answers a regional variant the same way.
611
+ const localeMaps = [];
612
+ for (let candidate = requested; candidate; ) {
613
+ const names = Object.keys(byLocale);
614
+ const lowered = candidate.toLowerCase();
615
+ const match = names.includes(candidate)
616
+ ? candidate
617
+ : names.filter((name) => name.toLowerCase() === lowered).sort()[0];
618
+ if (match !== undefined && !localeMaps.includes(byLocale[match])) {
619
+ localeMaps.push(byLocale[match]);
620
+ }
621
+ const lastSeparator = Math.max(candidate.lastIndexOf('-'), candidate.lastIndexOf('_'));
622
+ candidate = lastSeparator > 0 ? candidate.slice(0, lastSeparator) : undefined;
623
+ }
624
+ let template;
625
+ for (const localeMap of localeMaps) {
626
+ template = Object.hasOwn(localeMap, key) ? localeMap[key] : undefined;
627
+ if (template !== undefined) {
628
+ break;
629
+ }
630
+ }
609
631
  if (template === undefined) {
610
632
  template = Object.hasOwn(translations, key) ? translations[key] : undefined;
611
633
  }
@@ -101,6 +101,7 @@ export type MergedResource = MergedResourceRecord & {
101
101
  status: ResourceStatus;
102
102
  };
103
103
  export type ProgrammingLanguage = 'JAVASCRIPT' | 'PYTHON';
104
+ export declare const PROGRAMMING_LANGUAGES: ReadonlyArray<ProgrammingLanguage>;
104
105
  export declare const programmingLanguageDisplayName: (programmingLanguage: ProgrammingLanguage) => string;
105
106
  export type WebhookAuthMode = 'NONE' | 'TEAM' | 'CUSTOM';
106
107
  export declare const DEFAULT_WEBHOOK_AUTH_HEADER = "Authorization";
@@ -239,6 +240,20 @@ export declare class VersionedScriptLibrary extends Resource {
239
240
  constructor(id: string, tag: string, sourceCode: string, programmingLanguage: ProgrammingLanguage);
240
241
  get contentHash(): string;
241
242
  }
243
+ /**
244
+ * A published snapshot of the dependencies a workspace declares for one language. Pulled read-only
245
+ * for visibility and git history: unlike process and module versions, these are never pushed back —
246
+ * `fcode settings:versions:create` is what publishes them.
247
+ */
248
+ export declare class VersionedProgrammingLanguageManifest extends Resource {
249
+ id: string;
250
+ slug: string;
251
+ tag: string;
252
+ programmingLanguage: ProgrammingLanguage;
253
+ dependencies: Array<Dependency>;
254
+ constructor(tag: string, programmingLanguage: ProgrammingLanguage, dependencies: Array<Dependency>);
255
+ get contentHash(): string;
256
+ }
242
257
  export declare class Variable extends Resource {
243
258
  static ENV_KEY_MAX_LENGTH: number;
244
259
  static ENV_KEY_REGEX: RegExp;
@@ -272,8 +287,8 @@ export interface ErrorHandlerConfig {
272
287
  tag?: string | null;
273
288
  }
274
289
  export interface TeamVersionManifestEntry {
275
- resourceType: 'MODULE' | 'PROCESS';
276
- resourceId: string;
290
+ resourceType: 'DEPENDENCIES' | 'LOCALE' | 'MODULE' | 'PROCESS';
291
+ resourceId?: string | null;
277
292
  slug: string;
278
293
  outcome: 'CREATED' | 'FAILED' | 'SKIPPED_EXISTS' | 'SKIPPED_NO_TAG' | 'UPDATED';
279
294
  error?: string | null;
@@ -305,17 +320,22 @@ export interface TeamAliasFileEntry {
305
320
  name: string;
306
321
  tag: string;
307
322
  }
308
- export declare class Team extends Resource {
323
+ export interface ParentTeamRef {
324
+ slug: string;
325
+ version?: string;
326
+ }
327
+ /** The workspace's settings.json: what `fcode settings:pull|push` syncs against the cloud team. */
328
+ export declare class WorkspaceSettings extends Resource {
309
329
  id: string | undefined;
310
330
  slug: string;
311
331
  zoneId?: string;
312
332
  errorHandlerConfig?: ErrorHandlerConfig;
313
- parentTeamSlugs: string[];
333
+ parentTeams: ParentTeamRef[];
314
334
  webhookAuth?: WebhookAuth;
315
335
  primaryLocale?: string;
316
336
  versions?: TeamVersionFileEntry[];
317
337
  aliases?: TeamAliasFileEntry[];
318
- constructor(slug: string, parentTeamSlugs: string[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth, primaryLocale?: string);
338
+ constructor(slug: string, parentTeams: ParentTeamRef[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth, primaryLocale?: string);
319
339
  get contentHash(): string;
320
340
  }
321
341
  export interface RawErrorHandlerConfig {
@@ -326,13 +346,13 @@ export interface RawTeamSettings {
326
346
  slug: string;
327
347
  zoneId?: string;
328
348
  errorHandlerConfig?: RawErrorHandlerConfig | null;
329
- parentTeamSlugs: string[];
349
+ parentTeams: ParentTeamRef[];
330
350
  webhookAuth?: WebhookAuth | null;
331
351
  primaryLocale?: string | null;
332
352
  }
333
353
  export interface TeamUpdateInput {
334
354
  zoneId?: string;
335
- parentTeamSlugs?: string[];
355
+ parentTeams?: ParentTeamRef[];
336
356
  executionErrorHandlerConfig?: RawErrorHandlerConfig | null;
337
357
  webhookAuth?: {
338
358
  headerName?: string | null;
@@ -378,6 +398,9 @@ export declare const ForceFlag: {
378
398
  export declare const ResourceSlugArg: {
379
399
  resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
380
400
  };
401
+ export declare const ShowInheritedFlag: {
402
+ showInherited: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
403
+ };
381
404
  export declare const TeamNameArg: {
382
405
  team: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
383
406
  };
@@ -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.Locale = 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.ShowInheritedFlag = exports.ResourceSlugArg = exports.ForceFlag = exports.ScriptLibraryNotFound = exports.ProcessNotFound = exports.WorkspaceSettings = exports.ProgrammingLanguageManifest = exports.Variable = exports.VersionedProgrammingLanguageManifest = exports.VersionedScriptLibrary = exports.Locale = exports.ScriptLibrary = exports.VersionedProcess = exports.Process = exports.DEFAULT_WEBHOOK_AUTH_HEADER = exports.programmingLanguageDisplayName = exports.PROGRAMMING_LANGUAGES = 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");
@@ -28,6 +28,7 @@ var ResourceStatus;
28
28
  ResourceStatus["SENSITIVE"] = "\uD83D\uDD12 (sensitive)";
29
29
  ResourceStatus["SENSITIVITY_CHANGED"] = "\uD83D\uDEAB (sensitivity-changed)";
30
30
  })(ResourceStatus || (exports.ResourceStatus = ResourceStatus = {}));
31
+ exports.PROGRAMMING_LANGUAGES = ['JAVASCRIPT', 'PYTHON'];
31
32
  const programmingLanguageDisplayName = (programmingLanguage) => {
32
33
  switch (programmingLanguage) {
33
34
  case 'JAVASCRIPT':
@@ -67,7 +68,7 @@ class Process extends Resource {
67
68
  webhook: this.webhook || null,
68
69
  form: this.form || null,
69
70
  // Inserted only when set, so every process that predates UI triggers keeps the
70
- // content hash already stored in .fcode (mirrors Team.contentHash).
71
+ // content hash already stored in .fcode (mirrors WorkspaceSettings.contentHash).
71
72
  ...(this.uiTrigger && { uiTrigger: this.uiTrigger }),
72
73
  manifestDependencies: this.manifest.dependencies,
73
74
  dependenciesScopedToProcess: this.settings.dependencies.scopedToProcess,
@@ -156,6 +157,25 @@ class VersionedScriptLibrary extends Resource {
156
157
  }
157
158
  }
158
159
  exports.VersionedScriptLibrary = VersionedScriptLibrary;
160
+ /**
161
+ * A published snapshot of the dependencies a workspace declares for one language. Pulled read-only
162
+ * for visibility and git history: unlike process and module versions, these are never pushed back —
163
+ * `fcode settings:versions:create` is what publishes them.
164
+ */
165
+ class VersionedProgrammingLanguageManifest extends Resource {
166
+ constructor(tag, programmingLanguage, dependencies) {
167
+ super();
168
+ this.id = `${tag}#${programmingLanguage}`;
169
+ this.slug = programmingLanguage;
170
+ this.tag = tag;
171
+ this.programmingLanguage = programmingLanguage;
172
+ this.dependencies = dependencies;
173
+ }
174
+ get contentHash() {
175
+ return crypto.createHash('md5').update(JSON.stringify(this.dependencies)).digest('hex');
176
+ }
177
+ }
178
+ exports.VersionedProgrammingLanguageManifest = VersionedProgrammingLanguageManifest;
159
179
  class Variable extends Resource {
160
180
  constructor(id, key, value, isSensitive, resolvingTeamSlug) {
161
181
  super();
@@ -214,11 +234,12 @@ class ProgrammingLanguageManifest extends Resource {
214
234
  }
215
235
  }
216
236
  exports.ProgrammingLanguageManifest = ProgrammingLanguageManifest;
217
- class Team extends Resource {
218
- constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig, webhookAuth, primaryLocale) {
237
+ /** The workspace's settings.json: what `fcode settings:pull|push` syncs against the cloud team. */
238
+ class WorkspaceSettings extends Resource {
239
+ constructor(slug, parentTeams, zoneId, errorHandlerConfig, webhookAuth, primaryLocale) {
219
240
  super();
220
241
  this.slug = slug;
221
- this.parentTeamSlugs = parentTeamSlugs;
242
+ this.parentTeams = parentTeams;
222
243
  this.zoneId = zoneId;
223
244
  this.errorHandlerConfig = errorHandlerConfig;
224
245
  this.webhookAuth = webhookAuth;
@@ -229,16 +250,18 @@ class Team extends Resource {
229
250
  zoneId: this.zoneId || null,
230
251
  errorHandlerConfig: this.errorHandlerConfig || null,
231
252
  // Kept in the given order, not sorted: the order is the inheritance precedence, so a pure
232
- // reorder is a real change that must be detected and pushed.
233
- parentTeamSlugs: [...(this.parentTeamSlugs || [])],
253
+ // reorder is a real change that must be detected and pushed. Renamed from parentTeamSlugs
254
+ // when version pins arrived, so every workspace with parents reports settings.json modified
255
+ // once after upgrading — a deliberate one-time diff.
256
+ parentTeams: (this.parentTeams || []).map(({ slug, version }) => version ? { slug, version } : { slug }),
234
257
  };
235
- // Inserted only when set, so every team.json that predates webhook auth keeps the
258
+ // Inserted only when set, so every settings.json that predates webhook auth keeps the
236
259
  // hash already stored in .fcode and does not report a phantom conflict. A key present
237
260
  // with an undefined value would not do: canonicalStringify writes that as null.
238
261
  if (this.webhookAuth) {
239
262
  payload.webhookAuth = this.webhookAuth;
240
263
  }
241
- // Same insert-only-when-set rule, for the same reason: a team.json written before locales
264
+ // Same insert-only-when-set rule, for the same reason: a settings.json written before locales
242
265
  // existed must keep the hash already stored in .fcode.
243
266
  if (this.primaryLocale) {
244
267
  payload.primaryLocale = this.primaryLocale;
@@ -258,7 +281,7 @@ class Team extends Resource {
258
281
  return (0, canonical_hash_1.canonicalJsonHash)(payload);
259
282
  }
260
283
  }
261
- exports.Team = Team;
284
+ exports.WorkspaceSettings = WorkspaceSettings;
262
285
  class ProcessNotFound extends Error {
263
286
  }
264
287
  exports.ProcessNotFound = ProcessNotFound;
@@ -277,6 +300,11 @@ exports.ResourceSlugArg = {
277
300
  name: 'slug',
278
301
  }),
279
302
  };
303
+ exports.ShowInheritedFlag = {
304
+ showInherited: core_1.Flags.boolean({
305
+ description: 'Also list inherited resources that have no changes',
306
+ }),
307
+ };
280
308
  exports.TeamNameArg = {
281
309
  team: core_1.Args.string({ description: 'Team name', name: 'team' }),
282
310
  };
@@ -1,5 +1,12 @@
1
- import { Process, ResourceMetadata, VersionedProcess } from '../core/types';
1
+ import { Process, ProcessMetadataFields, ProgrammingLanguage, ResourceMetadata, VersionedProcess } from '../core/types';
2
2
  import ResourceRepositoryBase from './resource';
3
+ /**
4
+ * What a process writes to metadata.json. Exported so anything rendering a process as its files
5
+ * — `saveFiles` here, `fcode diff` — produces the same bytes for the same process.
6
+ */
7
+ export declare function buildProcessMetadata(process: Process): ProcessMetadataFields;
8
+ export declare const PROCESS_SOURCE_CODE_FILE_NAME: (programmingLanguage: string) => string;
9
+ export declare const PROCESS_DEPENDENCIES_FILE_NAME: (programmingLanguage: ProgrammingLanguage) => string;
3
10
  declare class ProcessRepository extends ResourceRepositoryBase<Process> {
4
11
  constructor();
5
12
  protected buildResourcePath(process: Process, filename: string, version?: string): string;
@@ -12,7 +19,6 @@ declare class ProcessRepository extends ResourceRepositoryBase<Process> {
12
19
  findSlugsNotAdded(): Promise<Array<string>>;
13
20
  find(processSlug: string, version?: string): Promise<Process>;
14
21
  private readMetadataFile;
15
- private buildMetadata;
16
22
  findParameters(processSlug: string): Promise<any>;
17
23
  getLastFileModifiedDate(process: Process, filename: string): Promise<Date>;
18
24
  protected getResourcePath(process: Process): string;
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROCESS_DEPENDENCIES_FILE_NAME = exports.PROCESS_SOURCE_CODE_FILE_NAME = void 0;
4
+ exports.buildProcessMetadata = buildProcessMetadata;
3
5
  const fs = require("fs-extra");
4
6
  const crypto = require("node:crypto");
5
7
  const path = require("node:path");
@@ -17,6 +19,30 @@ const PROGRAMMING_LANGUAGE_DEPENDENCIES_FILE_NAME = {
17
19
  JAVASCRIPT: 'package.json',
18
20
  PYTHON: 'requirements.txt',
19
21
  };
22
+ /**
23
+ * What a process writes to metadata.json. Exported so anything rendering a process as its files
24
+ * — `saveFiles` here, `fcode diff` — produces the same bytes for the same process.
25
+ */
26
+ function buildProcessMetadata(process) {
27
+ const metadata = { name: process.name, tags: process.tags ?? [] };
28
+ if (process.description !== undefined) {
29
+ metadata.description = process.description;
30
+ }
31
+ if (process.webhook !== undefined) {
32
+ metadata.webhook = process.webhook;
33
+ }
34
+ if (process.form !== undefined) {
35
+ metadata.form = process.form;
36
+ }
37
+ if (process.uiTrigger !== undefined) {
38
+ metadata.uiTrigger = process.uiTrigger;
39
+ }
40
+ return metadata;
41
+ }
42
+ const PROCESS_SOURCE_CODE_FILE_NAME = (programmingLanguage) => (programmingLanguage === 'PYTHON' ? 'main.py' : 'index.js');
43
+ exports.PROCESS_SOURCE_CODE_FILE_NAME = PROCESS_SOURCE_CODE_FILE_NAME;
44
+ const PROCESS_DEPENDENCIES_FILE_NAME = (programmingLanguage) => PROGRAMMING_LANGUAGE_DEPENDENCIES_FILE_NAME[programmingLanguage];
45
+ exports.PROCESS_DEPENDENCIES_FILE_NAME = PROCESS_DEPENDENCIES_FILE_NAME;
20
46
  class ProcessRepository extends resource_1.default {
21
47
  constructor() {
22
48
  super({
@@ -34,7 +60,7 @@ class ProcessRepository extends resource_1.default {
34
60
  return (0, filenamify_1.default)(process.slug);
35
61
  }
36
62
  getSourceCodeFileName(programmingLanguage) {
37
- return programmingLanguage === 'PYTHON' ? 'main.py' : 'index.js';
63
+ return (0, exports.PROCESS_SOURCE_CODE_FILE_NAME)(programmingLanguage);
38
64
  }
39
65
  async findAll() {
40
66
  const { processes } = await this.workspaceConfig.getActiveTeam();
@@ -146,22 +172,6 @@ class ProcessRepository extends resource_1.default {
146
172
  return { name: processSlug, tags: [] };
147
173
  }
148
174
  }
149
- buildMetadata(process) {
150
- const metadata = { name: process.name, tags: process.tags ?? [] };
151
- if (process.description !== undefined) {
152
- metadata.description = process.description;
153
- }
154
- if (process.webhook !== undefined) {
155
- metadata.webhook = process.webhook;
156
- }
157
- if (process.form !== undefined) {
158
- metadata.form = process.form;
159
- }
160
- if (process.uiTrigger !== undefined) {
161
- metadata.uiTrigger = process.uiTrigger;
162
- }
163
- return metadata;
164
- }
165
175
  async findParameters(processSlug) {
166
176
  const processMetadata = await this.getProcessMetadata(processSlug);
167
177
  if (!processMetadata) {
@@ -238,7 +248,7 @@ class ProcessRepository extends resource_1.default {
238
248
  readOnly,
239
249
  });
240
250
  (0, file_utils_1.outputFileSyncIfChanged)(this.buildResourcePath(process, 'README.md'), process.readme || '', { readOnly });
241
- (0, file_utils_1.outputFileSyncIfChanged)(this.buildResourcePath(process, 'metadata.json'), JSON.stringify(this.buildMetadata(process), null, 2), { readOnly });
251
+ (0, file_utils_1.outputFileSyncIfChanged)(this.buildResourcePath(process, 'metadata.json'), JSON.stringify(buildProcessMetadata(process), null, 2), { readOnly });
242
252
  (0, file_utils_1.outputFileSyncIfChanged)(this.buildResourcePath(process, 'parametersSchema.json'), JSON.stringify(process.script.parametersSchema, null, 2), { readOnly });
243
253
  const parametersFilePath = this.buildResourcePath(process, 'parameters.json');
244
254
  if (!fs.existsSync(parametersFilePath)) {
@@ -1,4 +1,4 @@
1
- import { ProgrammingLanguage, ProgrammingLanguageManifest, ResourceMetadata } from '../core/types';
1
+ import { ProgrammingLanguage, ProgrammingLanguageManifest, ResourceMetadata, VersionedProgrammingLanguageManifest } from '../core/types';
2
2
  import ResourceRepositoryBase from './resource';
3
3
  /**
4
4
  * The inherited file each language collapses its parents into. Exported so the gitignore sync names
@@ -10,6 +10,7 @@ declare class ProgrammingLanguageManifestRepository extends ResourceRepositoryBa
10
10
  constructor();
11
11
  find(slug: string): Promise<ProgrammingLanguageManifest>;
12
12
  protected getSourceCodeFileName(programmingLanguageManifest: ProgrammingLanguageManifest): string;
13
+ private getSourceCodeFileNameFor;
13
14
  /**
14
15
  * The inherited dependencies of one language, collapsed into a single file the way inherited
15
16
  * variables are (`variables.inherited.env`) rather than one file per parent: two versions of the
@@ -28,6 +29,19 @@ declare class ProgrammingLanguageManifestRepository extends ResourceRepositoryBa
28
29
  metadataFromFiles(slug: string): Promise<ResourceMetadata>;
29
30
  validSlug(slug: string): boolean;
30
31
  slugFormatMessage(): string;
32
+ /**
33
+ * Published snapshots live under `dependencies/versions/<tag>/`, one manifest file per language,
34
+ * mirroring the `versions/` folders of processes and modules. Written read-only: unlike those,
35
+ * these are pulled for visibility only — `fcode settings:versions:create` is what publishes them, and
36
+ * editing one here would push nothing.
37
+ */
38
+ private versionsDirectory;
39
+ private versionDirectory;
40
+ private versionPath;
41
+ getLocalVersionTags(): string[];
42
+ versionExistsLocally(version: VersionedProgrammingLanguageManifest): boolean;
43
+ removeVersion(tag: string): void;
44
+ saveVersion(version: VersionedProgrammingLanguageManifest): Promise<void>;
31
45
  findSlugsNotAdded(): Promise<Array<string>>;
32
46
  }
33
47
  export default ProgrammingLanguageManifestRepository;