@factorialco/fcode-cli 2.3.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 (123) hide show
  1. package/README.md +334 -117
  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/dependencies/status.d.ts +11 -0
  15. package/dist/commands/dependencies/status.js +43 -0
  16. package/dist/commands/diff.d.ts +12 -0
  17. package/dist/commands/diff.js +48 -0
  18. package/dist/commands/http.d.ts +1 -1
  19. package/dist/commands/http.js +7 -7
  20. package/dist/commands/i18n/diff.d.ts +9 -0
  21. package/dist/commands/i18n/diff.js +15 -0
  22. package/dist/commands/i18n/status.d.ts +3 -0
  23. package/dist/commands/i18n/status.js +1 -0
  24. package/dist/commands/modules/diff.d.ts +9 -0
  25. package/dist/commands/modules/diff.js +15 -0
  26. package/dist/commands/modules/status.d.ts +3 -0
  27. package/dist/commands/modules/status.js +1 -0
  28. package/dist/commands/processes/diff.d.ts +9 -0
  29. package/dist/commands/processes/diff.js +15 -0
  30. package/dist/commands/processes/status.d.ts +3 -0
  31. package/dist/commands/processes/status.js +1 -0
  32. package/dist/commands/pull.js +3 -3
  33. package/dist/commands/push.js +2 -2
  34. package/dist/commands/{team → settings}/aliases/delete.d.ts +1 -1
  35. package/dist/commands/{team → settings}/aliases/delete.js +6 -6
  36. package/dist/commands/{team → settings}/aliases/list.d.ts +1 -1
  37. package/dist/commands/{team → settings}/aliases/list.js +8 -8
  38. package/dist/commands/{team → settings}/aliases/set.d.ts +1 -1
  39. package/dist/commands/{team → settings}/aliases/set.js +8 -8
  40. package/dist/commands/settings/diff.d.ts +8 -0
  41. package/dist/commands/settings/diff.js +39 -0
  42. package/dist/commands/{team/push.d.ts → settings/pull.d.ts} +3 -3
  43. package/dist/commands/settings/pull.js +41 -0
  44. package/dist/commands/settings/push.d.ts +13 -0
  45. package/dist/commands/settings/push.js +103 -0
  46. package/dist/commands/settings/status.d.ts +8 -0
  47. package/dist/commands/settings/status.js +46 -0
  48. package/dist/commands/{team → settings}/versions/create.d.ts +1 -2
  49. package/dist/commands/settings/versions/create.js +36 -0
  50. package/dist/commands/{team → settings}/versions/delete.d.ts +1 -1
  51. package/dist/commands/{team → settings}/versions/delete.js +10 -10
  52. package/dist/commands/{team → settings}/versions/list.d.ts +1 -1
  53. package/dist/commands/{team → settings}/versions/list.js +8 -8
  54. package/dist/commands/status.d.ts +3 -0
  55. package/dist/commands/status.js +10 -7
  56. package/dist/commands/team/clone.d.ts +14 -0
  57. package/dist/commands/team/clone.js +91 -0
  58. package/dist/commands/team/pull.d.ts +3 -4
  59. package/dist/commands/team/pull.js +25 -19
  60. package/dist/commands/team/status.d.ts +3 -4
  61. package/dist/commands/team/status.js +34 -27
  62. package/dist/commands/variables/diff.d.ts +9 -0
  63. package/dist/commands/variables/diff.js +15 -0
  64. package/dist/commands/variables/status.d.ts +3 -0
  65. package/dist/commands/variables/status.js +1 -0
  66. package/dist/config/dev-team.d.ts +48 -0
  67. package/dist/config/dev-team.js +62 -0
  68. package/dist/config/workspace.d.ts +2 -2
  69. package/dist/config/workspace.js +4 -2
  70. package/dist/core/commands/base-command.js +5 -0
  71. package/dist/core/commands/dependency-command.d.ts +2 -1
  72. package/dist/core/commands/dependency-command.js +3 -0
  73. package/dist/core/commands/i18n-command.js +2 -0
  74. package/dist/core/commands/modules-command.js +2 -0
  75. package/dist/core/commands/processes-command.js +2 -0
  76. package/dist/core/commands/resource-command.d.ts +26 -1
  77. package/dist/core/commands/resource-command.js +100 -3
  78. package/dist/core/commands/variables-command.d.ts +7 -0
  79. package/dist/core/commands/variables-command.js +13 -0
  80. package/dist/core/dependencies/env-dependencies-command.js +6 -3
  81. package/dist/core/executors/javascript-execution-task.js +36 -5
  82. package/dist/core/types.d.ts +59 -9
  83. package/dist/core/types.js +64 -10
  84. package/dist/core/utils/dependencies.js +4 -1
  85. package/dist/env.js +1 -1
  86. package/dist/repositories/process.d.ts +8 -2
  87. package/dist/repositories/process.js +29 -15
  88. package/dist/repositories/programming-language-manifest.d.ts +31 -1
  89. package/dist/repositories/programming-language-manifest.js +96 -2
  90. package/dist/repositories/settings.d.ts +52 -0
  91. package/dist/repositories/settings.js +214 -0
  92. package/dist/runtime/libs/python/fcode/context.py +42 -8
  93. package/dist/services/dashboard-client.d.ts +31 -0
  94. package/dist/services/dashboard-client.js +99 -0
  95. package/dist/services/dev-team-service.d.ts +41 -0
  96. package/dist/services/dev-team-service.js +162 -0
  97. package/dist/services/fcode-graphql-client.d.ts +17 -2
  98. package/dist/services/fcode-graphql-client.js +127 -15
  99. package/dist/services/locale-resolver.d.ts +17 -1
  100. package/dist/services/locale-resolver.js +62 -7
  101. package/dist/services/resource-differ.d.ts +60 -0
  102. package/dist/services/resource-differ.js +135 -0
  103. package/dist/services/skills-installer.d.ts +16 -0
  104. package/dist/services/skills-installer.js +77 -0
  105. package/dist/services/team-versioning.d.ts +16 -0
  106. package/dist/services/team-versioning.js +40 -0
  107. package/dist/services/webhook-auth.js +1 -1
  108. package/dist/services/workspace-cloner.d.ts +31 -0
  109. package/dist/services/workspace-cloner.js +95 -0
  110. package/dist/utils/dev-slug.d.ts +12 -0
  111. package/dist/utils/dev-slug.js +37 -0
  112. package/dist/utils/diff.d.ts +19 -0
  113. package/dist/utils/diff.js +58 -0
  114. package/dist/utils/gitignore.d.ts +3 -1
  115. package/dist/utils/gitignore.js +11 -2
  116. package/dist/utils/slugify.d.ts +17 -0
  117. package/dist/utils/slugify.js +41 -0
  118. package/oclif.manifest.json +433 -113
  119. package/package.json +12 -7
  120. package/dist/commands/team/push.js +0 -52
  121. package/dist/commands/team/versions/create.js +0 -42
  122. package/dist/repositories/team.d.ts +0 -31
  123. package/dist/repositories/team.js +0 -127
@@ -9,20 +9,8 @@ export default class Clone extends Command {
9
9
  static flags: {
10
10
  skipSkillsSetup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
11
  };
12
- checkWorkspaceAlreadyCloned(slug: string): Promise<void>;
13
- checkWorkspaceIsEmpty(slug: string): Promise<void>;
14
12
  cloneTeam(origin: TeamOrigin, skipSkillsSetup?: boolean): Promise<void>;
15
- createWorkspaceConfig(origin: TeamOrigin): Promise<void>;
16
- createWorkspaceI18n(slug: string): void;
17
- createWorkspaceDatastore(slug: string): void;
18
- createWorkspaceStorage(slug: string): void;
19
- createWorkspaceGitIgnore(slug: string): Promise<void>;
20
- createWorkspaceLocalVariables(slug: string): Promise<void>;
21
- private skillsInstallHint;
22
- setupSkills(): Promise<void>;
23
- private installSkills;
24
13
  catch(error: any): Promise<void>;
25
14
  init(): Promise<void>;
26
- mkdirWorkspace(slug: string): Promise<void>;
27
15
  run(): Promise<void>;
28
16
  }
@@ -1,125 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const node_child_process_1 = require("node:child_process");
5
- const fs = require("fs-extra");
6
4
  const path = require("node:path");
7
5
  const global_1 = require("../config/global");
8
- const workspace_1 = require("../config/workspace");
9
6
  const pull_1 = require("./pull");
10
7
  const team_access_1 = require("../services/team-access");
8
+ const skills_installer_1 = require("../services/skills-installer");
9
+ const workspace_cloner_1 = require("../services/workspace-cloner");
11
10
  const types_1 = require("../core/types");
12
11
  const cli_1 = require("../utils/cli");
13
12
  const settings_1 = require("../config/settings");
14
- // Skill sources installed on clone. `release-sdk` in factorial-api-sdks is a
15
- // maintainer-only skill, so we pick `factorial-api-sdks` explicitly there.
16
- const SKILLS_SOURCES = [
17
- { repo: 'factorialco/factorial-code-skills', skills: '*' },
18
- { repo: 'factorialco/factorial-api-sdks', skills: 'factorial-api-sdks' },
19
- ];
20
13
  class Clone extends core_1.Command {
21
- async checkWorkspaceAlreadyCloned(slug) {
14
+ async cloneTeam(origin, skipSkillsSetup = false) {
15
+ const workspaceDir = path.resolve(origin.slug);
22
16
  try {
23
- await new workspace_1.default(path.join(process.cwd(), slug)).getActiveTeam();
24
- }
25
- catch {
26
- return;
27
- }
28
- this.error(`Workspace '${slug}' already cloned.`, {
29
- code: 'ERR_WORKSPACE_ALREADY_CLONED',
30
- exit: 1,
31
- suggestions: [`cd ${slug}`, 'fcode pull'],
32
- });
33
- }
34
- async checkWorkspaceIsEmpty(slug) {
35
- const files = fs.readdirSync(slug);
36
- if (files.length == 0) {
37
- return;
17
+ await (0, workspace_cloner_1.scaffoldWorkspace)(workspaceDir, origin);
38
18
  }
39
- const envValue = process.env.IGNORE_WORKSPACE_NOT_EMPTY;
40
- const ignoreWorkspaceNotEmpty = envValue === 'true' || envValue === 'false'
41
- ? envValue === 'true'
42
- : (await new global_1.default().getSetting(settings_1.SettingKeys.IGNORE_WORKSPACE_NOT_EMPTY)) === 'true';
43
- if (!ignoreWorkspaceNotEmpty) {
44
- this.error(`Destination path '${slug}' already exists and is not empty.`, {
45
- code: 'ERR_WORKSPACE_DIR_IS_NOT_EMPTY',
46
- exit: 1,
47
- });
19
+ catch (error) {
20
+ if (error instanceof workspace_cloner_1.WorkspaceAlreadyCloned) {
21
+ this.error(`Workspace '${origin.slug}' already cloned.`, {
22
+ code: 'ERR_WORKSPACE_ALREADY_CLONED',
23
+ exit: 1,
24
+ suggestions: [`cd ${origin.slug}`, 'fcode pull'],
25
+ });
26
+ }
27
+ if (error instanceof workspace_cloner_1.WorkspaceNotEmpty) {
28
+ this.error(`Destination path '${origin.slug}' already exists and is not empty.`, {
29
+ code: 'ERR_WORKSPACE_DIR_IS_NOT_EMPTY',
30
+ exit: 1,
31
+ });
32
+ }
33
+ throw error;
48
34
  }
49
- }
50
- async cloneTeam(origin, skipSkillsSetup = false) {
51
- await this.mkdirWorkspace(origin.slug);
52
- await this.createWorkspaceConfig(origin);
53
- await this.createWorkspaceGitIgnore(origin.slug);
54
- this.createWorkspaceI18n(origin.slug);
55
- this.createWorkspaceDatastore(origin.slug);
56
- this.createWorkspaceStorage(origin.slug);
57
- await this.createWorkspaceLocalVariables(origin.slug);
58
- process.chdir(origin.slug);
59
- await pull_1.default.run([]);
35
+ await (0, workspace_cloner_1.runInWorkspace)(workspaceDir, () => pull_1.default.run([]));
60
36
  if (!skipSkillsSetup) {
61
- await this.setupSkills();
37
+ await (0, skills_installer_1.installSkillsAt)(workspaceDir);
62
38
  }
63
39
  }
64
- async createWorkspaceConfig(origin) {
65
- const config = new workspace_1.default(path.join(process.cwd(), origin.slug));
66
- await config.init(origin);
67
- }
68
- // Created empty so `i18n:add` has somewhere to look before the first locale is pulled.
69
- createWorkspaceI18n(slug) {
70
- fs.mkdirpSync(path.join(slug, 'i18n'));
71
- }
72
- createWorkspaceDatastore(slug) {
73
- fs.outputFileSync(path.join(slug, 'datastore.json'), '{}');
74
- }
75
- createWorkspaceStorage(slug) {
76
- fs.mkdirSync(path.join(slug, 'storage'));
77
- }
78
- async createWorkspaceGitIgnore(slug) {
79
- const globalConfig = new global_1.default();
80
- const localVariablesFileName = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
81
- const gitIgnorePatterns = [localVariablesFileName, 'datastore.json', '.env', 'storage/*'];
82
- fs.outputFileSync(path.join(slug, '.gitignore'), gitIgnorePatterns.join('\n'));
83
- }
84
- async createWorkspaceLocalVariables(slug) {
85
- const globalConfig = new global_1.default();
86
- const localVariablesFileName = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
87
- fs.outputFileSync(path.join(slug, localVariablesFileName), '');
88
- }
89
- skillsInstallHint(repo, skills) {
90
- cli_1.default.log('💡 Install the Factorial Code agent skills later with:');
91
- cli_1.default.log(` npx skills add ${repo} -s '${skills}' -y`);
92
- }
93
- async setupSkills() {
94
- cli_1.default.log('');
95
- // The skills CLI only installs to agents it detects; a fresh workspace has no
96
- // agent directories, so create .claude/ first so Claude Code is always targeted.
97
- fs.ensureDirSync('.claude');
98
- for (const source of SKILLS_SOURCES) {
99
- await this.installSkills(source.repo, source.skills);
100
- }
101
- }
102
- async installSkills(repo, skills) {
103
- // `npx --yes skills add <repo> -s <skills> -y` — non-interactive install with
104
- // the skills CLI defaults: project scope (no -g), symlinked (no --copy).
105
- const skillsArgs = ['--yes', 'skills', 'add', repo, '-s', skills, '-y'];
106
- await new Promise((resolve) => {
107
- const child = (0, node_child_process_1.spawn)('npx', skillsArgs, { stdio: 'inherit' });
108
- child.on('error', () => {
109
- // npx not available (or failed to launch) — don't fail the clone, just tell the user how to do it.
110
- cli_1.default.log("\n⚠️ Couldn't run npx automatically (is Node.js / npx installed?).");
111
- this.skillsInstallHint(repo, skills);
112
- resolve();
113
- });
114
- child.on('close', (code) => {
115
- if (code !== 0) {
116
- cli_1.default.log("\n⚠️ Skill installation didn't complete.");
117
- this.skillsInstallHint(repo, skills);
118
- }
119
- resolve();
120
- });
121
- });
122
- }
123
40
  async catch(error) {
124
41
  process.exitCode = 1;
125
42
  const suggestions = error?.suggestions ?? [];
@@ -132,36 +49,6 @@ class Clone extends core_1.Command {
132
49
  const { authUrl } = await new global_1.default().getAuthInfo(url);
133
50
  await this.config.runHook('auth', { argv: [url, authUrl], id: 'login' });
134
51
  }
135
- async mkdirWorkspace(slug) {
136
- return new Promise((resolve, reject) => {
137
- fs.mkdir(slug, {}, (error) => {
138
- if (error) {
139
- if (error.code === 'EEXIST') {
140
- this.checkWorkspaceAlreadyCloned(slug)
141
- .then(() => this.checkWorkspaceIsEmpty(slug))
142
- .then(() => resolve())
143
- .catch((err) => reject(err));
144
- }
145
- else {
146
- reject(error);
147
- }
148
- }
149
- else {
150
- const globalConfig = new global_1.default();
151
- globalConfig
152
- .getSetting(settings_1.SettingKeys.VARIABLES_FILE_NAME)
153
- .then((variablesFileName) => {
154
- fs.mkdirSync(path.join(slug, 'processes'));
155
- fs.mkdirSync(path.join(slug, 'modules'));
156
- fs.mkdirSync(path.join(slug, 'dependencies'));
157
- fs.createFileSync(path.join(slug, variablesFileName));
158
- resolve();
159
- })
160
- .catch((err) => reject(err));
161
- }
162
- });
163
- });
164
- }
165
52
  async run() {
166
53
  const { args, flags } = await this.parse(Clone);
167
54
  let { slug, url } = (0, global_1.parseTeamOrigin)(args.team || '');
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class SettingsGet extends Command {
2
+ export default class ConfigGet extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -5,9 +5,9 @@ const global_1 = require("../../config/global");
5
5
  const settings_1 = require("../../config/settings");
6
6
  const cli_1 = require("../../utils/cli");
7
7
  const color_1 = require("@oclif/color");
8
- class SettingsGet extends core_1.Command {
8
+ class ConfigGet extends core_1.Command {
9
9
  async run() {
10
- const { args, flags } = await this.parse(SettingsGet);
10
+ const { args, flags } = await this.parse(ConfigGet);
11
11
  const { key } = args;
12
12
  try {
13
13
  const globalConfig = new global_1.default();
@@ -49,19 +49,19 @@ class SettingsGet extends core_1.Command {
49
49
  }
50
50
  }
51
51
  }
52
- SettingsGet.description = 'Get a setting value or all settings';
53
- SettingsGet.examples = [`$ fcode settings:get ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER}`, '$ fcode settings:get', '$ fcode settings:get --all'];
54
- SettingsGet.args = {
52
+ ConfigGet.description = 'Get a CLI setting value or all CLI settings';
53
+ ConfigGet.examples = [`$ fcode config:get ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER}`, '$ fcode config:get', '$ fcode config:get --all'];
54
+ ConfigGet.args = {
55
55
  key: core_1.Args.string({
56
56
  description: 'Setting key (optional, shows all settings if omitted)',
57
57
  name: 'key',
58
58
  required: false,
59
59
  }),
60
60
  };
61
- SettingsGet.flags = {
61
+ ConfigGet.flags = {
62
62
  all: core_1.Flags.boolean({
63
63
  char: 'a',
64
64
  description: 'Show all settings including defaults',
65
65
  }),
66
66
  };
67
- exports.default = SettingsGet;
67
+ exports.default = ConfigGet;
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class SettingsRemove extends Command {
2
+ export default class ConfigRemove extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -5,9 +5,9 @@ const global_1 = require("../../config/global");
5
5
  const cli_1 = require("../../utils/cli");
6
6
  const color_1 = require("@oclif/color");
7
7
  const settings_1 = require("../../config/settings");
8
- class SettingsRemove extends core_1.Command {
8
+ class ConfigRemove extends core_1.Command {
9
9
  async run() {
10
- const { args } = await this.parse(SettingsRemove);
10
+ const { args } = await this.parse(ConfigRemove);
11
11
  const { key } = args;
12
12
  try {
13
13
  const globalConfig = new global_1.default();
@@ -23,13 +23,13 @@ class SettingsRemove extends core_1.Command {
23
23
  }
24
24
  }
25
25
  }
26
- SettingsRemove.description = 'Remove a setting value';
27
- SettingsRemove.examples = [`$ fcode settings:remove ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER}`];
28
- SettingsRemove.args = {
26
+ ConfigRemove.description = 'Remove a CLI setting value';
27
+ ConfigRemove.examples = [`$ fcode config:remove ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER}`];
28
+ ConfigRemove.args = {
29
29
  key: core_1.Args.string({
30
30
  description: 'Setting key',
31
31
  name: 'key',
32
32
  required: true,
33
33
  }),
34
34
  };
35
- exports.default = SettingsRemove;
35
+ exports.default = ConfigRemove;
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class SettingsSet extends Command {
2
+ export default class ConfigSet extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -5,9 +5,9 @@ const global_1 = require("../../config/global");
5
5
  const cli_1 = require("../../utils/cli");
6
6
  const color_1 = require("@oclif/color");
7
7
  const settings_1 = require("../../config/settings");
8
- class SettingsSet extends core_1.Command {
8
+ class ConfigSet extends core_1.Command {
9
9
  async run() {
10
- const { args } = await this.parse(SettingsSet);
10
+ const { args } = await this.parse(ConfigSet);
11
11
  const { key, value } = args;
12
12
  try {
13
13
  const globalConfig = new global_1.default();
@@ -19,12 +19,12 @@ class SettingsSet extends core_1.Command {
19
19
  }
20
20
  }
21
21
  }
22
- SettingsSet.description = 'Set a setting value';
23
- SettingsSet.examples = [
24
- `$ fcode settings:set ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER} true`,
25
- `$ fcode settings:set ${settings_1.SettingKeys.IGNORE_WORKSPACE_NOT_EMPTY} false`,
22
+ ConfigSet.description = 'Set a CLI setting value';
23
+ ConfigSet.examples = [
24
+ `$ fcode config:set ${settings_1.SettingKeys.DISABLE_UPDATE_NOTIFIER} true`,
25
+ `$ fcode config:set ${settings_1.SettingKeys.IGNORE_WORKSPACE_NOT_EMPTY} false`,
26
26
  ];
27
- SettingsSet.args = {
27
+ ConfigSet.args = {
28
28
  key: core_1.Args.string({
29
29
  description: 'Setting key',
30
30
  name: 'key',
@@ -36,4 +36,4 @@ SettingsSet.args = {
36
36
  required: true,
37
37
  }),
38
38
  };
39
- exports.default = SettingsSet;
39
+ exports.default = ConfigSet;
@@ -0,0 +1,9 @@
1
+ import DependencyCommand from '../../core/commands/dependency-command';
2
+ export default class DependenciesDiff extends DependencyCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const types_1 = require("../../core/types");
4
+ const dependency_command_1 = require("../../core/commands/dependency-command");
5
+ class DependenciesDiff extends dependency_command_1.default {
6
+ async run() {
7
+ if (await this.resourcesDiff()) {
8
+ this.exit(1);
9
+ }
10
+ }
11
+ }
12
+ DependenciesDiff.description = 'Show a unified diff between local dependency manifests and the cloud. Exits 1 when they differ';
13
+ DependenciesDiff.examples = ['$ fcode dependencies:diff', '$ fcode dependencies:diff JAVASCRIPT'];
14
+ DependenciesDiff.args = types_1.ResourceSlugArg;
15
+ exports.default = DependenciesDiff;
@@ -8,5 +8,14 @@ export default class DependenciesPull extends DependencyCommand {
8
8
  static flags: {
9
9
  force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
10
  };
11
+ /**
12
+ * Writes the published snapshots into `dependencies/versions/<tag>/`, and drops the folders of
13
+ * tags deleted in the cloud — the same reconciliation `processes:pull` does. Unlike processes,
14
+ * there is no push counterpart: these files are a read-only record of what each release installs.
15
+ *
16
+ * A tag folder holds one file per language, so it is only removed when no language still publishes
17
+ * it — which is why pulling a single language leaves the other's files (and the folder) alone.
18
+ */
19
+ pullVersioned(): Promise<void>;
11
20
  run(): Promise<void>;
12
21
  }
@@ -1,10 +1,53 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const types_1 = require("../../core/types");
3
+ const color_1 = require("@oclif/color");
4
4
  const dependency_command_1 = require("../../core/commands/dependency-command");
5
+ const types_1 = require("../../core/types");
6
+ const cli_1 = require("../../utils/cli");
5
7
  class DependenciesPull extends dependency_command_1.default {
8
+ /**
9
+ * Writes the published snapshots into `dependencies/versions/<tag>/`, and drops the folders of
10
+ * tags deleted in the cloud — the same reconciliation `processes:pull` does. Unlike processes,
11
+ * there is no push counterpart: these files are a read-only record of what each release installs.
12
+ *
13
+ * A tag folder holds one file per language, so it is only removed when no language still publishes
14
+ * it — which is why pulling a single language leaves the other's files (and the folder) alone.
15
+ */
16
+ async pullVersioned() {
17
+ const repository = this._resourceRepository;
18
+ const languages = types_1.PROGRAMMING_LANGUAGES.filter((language) => this.filterResource(language));
19
+ const remoteTags = new Set();
20
+ for (const programmingLanguage of languages) {
21
+ const versions = await this._versionedResourceQuery(programmingLanguage);
22
+ if (versions === null) {
23
+ // The api predates dependency versioning: nothing to write, and no cloud state to
24
+ // reconcile the local folders against — deleting them would misread "cannot say" as
25
+ // "none published".
26
+ return;
27
+ }
28
+ for (const version of versions) {
29
+ remoteTags.add(version.tag);
30
+ if (!repository.versionExistsLocally(version)) {
31
+ cli_1.default.log(color_1.default.cyan(`🗂️ pulling ${programmingLanguage} dependencies version '${version.tag}'...`));
32
+ }
33
+ await repository.saveVersion(version);
34
+ }
35
+ }
36
+ // Only reconcile deletions on a full pull: with a single language asked for, a tag missing
37
+ // here may still be published by the other one.
38
+ if (languages.length < types_1.PROGRAMMING_LANGUAGES.length) {
39
+ return;
40
+ }
41
+ for (const localTag of repository.getLocalVersionTags()) {
42
+ if (!remoteTags.has(localTag)) {
43
+ cli_1.default.log(color_1.default.cyan(`🗑️ removing dependencies version '${localTag}'...`));
44
+ repository.removeVersion(localTag);
45
+ }
46
+ }
47
+ }
6
48
  async run() {
7
49
  await super.pullResources();
50
+ await this.pullVersioned();
8
51
  }
9
52
  }
10
53
  DependenciesPull.description = 'Fetch all dependencies from cloud and save them locally';
@@ -1,9 +1,20 @@
1
+ import { table } from '../../utils/cli/styled/table';
1
2
  import DependencyCommand from '../../core/commands/dependency-command';
3
+ import { type MergedResource } from '../../core/types';
4
+ export declare const inheritedCount: (resource: MergedResource) => number;
5
+ /**
6
+ * A manifest is never itself inherited — every workspace owns one per language — so the column
7
+ * counts the entries its parents contribute rather than naming one workspace the way the other
8
+ * `status` commands do. Empty when nothing is inherited, so the column is dropped entirely, like
9
+ * theirs.
10
+ */
11
+ export declare const inheritedColumn: (resources: Array<MergedResource>) => table.Columns<MergedResource>;
2
12
  export default class DependenciesStatus extends DependencyCommand {
3
13
  static description: string;
4
14
  static examples: string[];
5
15
  static args: {
6
16
  resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
17
  };
18
+ resourcesTableLog(resources: Array<MergedResource>): Promise<void>;
8
19
  run(): Promise<void>;
9
20
  }
@@ -1,8 +1,51 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inheritedColumn = exports.inheritedCount = void 0;
4
+ const cli_1 = require("../../utils/cli");
3
5
  const dependency_command_1 = require("../../core/commands/dependency-command");
4
6
  const types_1 = require("../../core/types");
7
+ const colorize_resource_1 = require("../../utils/colorize-resource");
8
+ const inheritedCount = (resource) => resource.inheritedDependencies?.length ?? 0;
9
+ exports.inheritedCount = inheritedCount;
10
+ /**
11
+ * A manifest is never itself inherited — every workspace owns one per language — so the column
12
+ * counts the entries its parents contribute rather than naming one workspace the way the other
13
+ * `status` commands do. Empty when nothing is inherited, so the column is dropped entirely, like
14
+ * theirs.
15
+ */
16
+ const inheritedColumn = (resources) => {
17
+ if (!resources.some((resource) => (0, exports.inheritedCount)(resource) > 0)) {
18
+ return {};
19
+ }
20
+ return {
21
+ inherited: {
22
+ get(resource) {
23
+ const count = (0, exports.inheritedCount)(resource);
24
+ return (0, colorize_resource_1.colorizeByStatus)(count > 0 ? `🔗 ${count}` : '', resource.status);
25
+ },
26
+ header: 'inherited',
27
+ },
28
+ };
29
+ };
30
+ exports.inheritedColumn = inheritedColumn;
5
31
  class DependenciesStatus extends dependency_command_1.default {
32
+ async resourcesTableLog(resources) {
33
+ cli_1.cli.table(resources, {
34
+ name: {
35
+ get({ slug, status }) {
36
+ return (0, colorize_resource_1.colorizeByStatus)(slug, status);
37
+ },
38
+ header: 'name',
39
+ },
40
+ ...(0, exports.inheritedColumn)(resources),
41
+ status: {
42
+ get({ status }) {
43
+ return (0, colorize_resource_1.colorizeByStatus)(status, status);
44
+ },
45
+ header: 'status',
46
+ },
47
+ }, { sort: 'name' });
48
+ }
6
49
  async run() {
7
50
  await this.resourcesLog();
8
51
  }
@@ -0,0 +1,12 @@
1
+ import BaseCommand from '../core/commands/base-command';
2
+ /**
3
+ * Runs one resource's diff and reports whether it found changes, instead of letting its
4
+ * `this.exit(1)` end the process: every resource must be diffed before the aggregate can
5
+ * exit with a verdict for all of them.
6
+ */
7
+ export declare function runCollectingExit(run: () => Promise<unknown>): Promise<boolean>;
8
+ export default class Diff extends BaseCommand {
9
+ static description: string;
10
+ static examples: string[];
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCollectingExit = runCollectingExit;
4
+ const core_1 = require("@oclif/core");
5
+ const base_command_1 = require("../core/commands/base-command");
6
+ const diff_1 = require("./modules/diff");
7
+ const diff_2 = require("./processes/diff");
8
+ const diff_3 = require("./variables/diff");
9
+ const diff_4 = require("./i18n/diff");
10
+ const diff_5 = require("./dependencies/diff");
11
+ const diff_6 = require("./settings/diff");
12
+ /**
13
+ * Runs one resource's diff and reports whether it found changes, instead of letting its
14
+ * `this.exit(1)` end the process: every resource must be diffed before the aggregate can
15
+ * exit with a verdict for all of them.
16
+ */
17
+ async function runCollectingExit(run) {
18
+ try {
19
+ await run();
20
+ return false;
21
+ }
22
+ catch (error) {
23
+ if (error instanceof core_1.Errors.ExitError && error.oclif.exit === 1) {
24
+ return true;
25
+ }
26
+ throw error;
27
+ }
28
+ }
29
+ class Diff extends base_command_1.default {
30
+ async run() {
31
+ await this.parse(Diff);
32
+ await this.logActiveTeam();
33
+ const results = [
34
+ await runCollectingExit(() => diff_2.default.run([])),
35
+ await runCollectingExit(() => diff_1.default.run([])),
36
+ await runCollectingExit(() => diff_3.default.run([])),
37
+ await runCollectingExit(() => diff_4.default.run([])),
38
+ await runCollectingExit(() => diff_5.default.run([])),
39
+ await runCollectingExit(() => diff_6.default.run([])),
40
+ ];
41
+ if (results.some(Boolean)) {
42
+ this.exit(1);
43
+ }
44
+ }
45
+ }
46
+ Diff.description = 'Show a unified diff between the local workspace and the cloud. Exits 1 when they differ';
47
+ Diff.examples = ['$ fcode diff'];
48
+ exports.default = Diff;
@@ -14,7 +14,7 @@ export default class Http extends Command {
14
14
  * Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
15
15
  * configuration names a variable, whose value is looked up in the workspace variables and compared
16
16
  * against the configured header. A missing or malformed header, an undefined variable, a mismatch,
17
- * or a webhook inheriting a workspace configuration that team.json does not have, is rejected.
17
+ * or a webhook inheriting a workspace configuration that settings.json does not have, is rejected.
18
18
  * Returns true when the request may proceed, false after writing a 403.
19
19
  */
20
20
  private _checkWebhookAuth;
@@ -7,7 +7,7 @@ const process_runner_1 = require("../services/process-runner");
7
7
  const process_1 = require("../repositories/process");
8
8
  const variable_1 = require("../repositories/variable");
9
9
  const workspace_1 = require("../config/workspace");
10
- const team_1 = require("../repositories/team");
10
+ const settings_1 = require("../repositories/settings");
11
11
  const webhook_auth_1 = require("../services/webhook-auth");
12
12
  const types_1 = require("../core/types");
13
13
  class Http extends core_1.Command {
@@ -19,7 +19,7 @@ class Http extends core_1.Command {
19
19
  : null;
20
20
  const { slug: teamSlug } = await new workspace_1.default().getTeamOrigin();
21
21
  const processRepository = new process_1.default();
22
- const teamRepository = new team_1.default();
22
+ const settingsRepository = new settings_1.default();
23
23
  if (useBasicAuth) {
24
24
  this.warn(`--auth-user/--auth-password protect the whole server through the ${types_1.DEFAULT_WEBHOOK_AUTH_HEADER} header, ` +
25
25
  `so a webhook expecting its credential there cannot be exercised at the same time.`);
@@ -86,7 +86,7 @@ class Http extends core_1.Command {
86
86
  res.end(JSON.stringify({ success: false, error: `Process slug required in path (e.g. /api/${teamSlug}/webhooks/my-process)` }));
87
87
  return;
88
88
  }
89
- if (!(await this._checkWebhookAuth(req, res, processRepository, teamRepository, slug))) {
89
+ if (!(await this._checkWebhookAuth(req, res, processRepository, settingsRepository, slug))) {
90
90
  return;
91
91
  }
92
92
  await this._handleProcessExecution(req, res, slug, urlObj, logLevel, jsonLogs);
@@ -122,10 +122,10 @@ class Http extends core_1.Command {
122
122
  * Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
123
123
  * configuration names a variable, whose value is looked up in the workspace variables and compared
124
124
  * against the configured header. A missing or malformed header, an undefined variable, a mismatch,
125
- * or a webhook inheriting a workspace configuration that team.json does not have, is rejected.
125
+ * or a webhook inheriting a workspace configuration that settings.json does not have, is rejected.
126
126
  * Returns true when the request may proceed, false after writing a 403.
127
127
  */
128
- async _checkWebhookAuth(req, res, processRepository, teamRepository, slug) {
128
+ async _checkWebhookAuth(req, res, processRepository, settingsRepository, slug) {
129
129
  let webhook;
130
130
  try {
131
131
  webhook = (await processRepository.find(slug)).webhook;
@@ -134,9 +134,9 @@ class Http extends core_1.Command {
134
134
  // Unknown process: let the execution path report the 404.
135
135
  return true;
136
136
  }
137
- // Read per request, like the process metadata above, so editing team.json while the server
137
+ // Read per request, like the process metadata above, so editing settings.json while the server
138
138
  // runs takes effect without a restart.
139
- const teamWebhookAuth = (await teamRepository.find()).webhookAuth;
139
+ const teamWebhookAuth = (await settingsRepository.find()).webhookAuth;
140
140
  let effectiveAuth;
141
141
  try {
142
142
  effectiveAuth = (0, webhook_auth_1.resolveEffectiveAuth)(webhook, teamWebhookAuth);
@@ -0,0 +1,9 @@
1
+ import I18nCommand from '../../core/commands/i18n-command';
2
+ export default class I18nDiff extends I18nCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ run(): Promise<void>;
9
+ }