@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
@@ -6,27 +6,27 @@ const api_client_command_1 = require("../../../core/commands/api-client-command"
6
6
  const pull_1 = require("../../pull");
7
7
  const types_1 = require("../../../core/types");
8
8
  const cli_1 = require("../../../utils/cli");
9
- class TeamVersionsDeleteCommand extends api_client_command_1.default {
9
+ class SettingsVersionsDeleteCommand extends api_client_command_1.default {
10
10
  async run() {
11
- const { args: { tag }, flags: { force }, } = await this.parse(TeamVersionsDeleteCommand);
11
+ const { args: { tag }, flags: { force }, } = await this.parse(SettingsVersionsDeleteCommand);
12
12
  if (!force) {
13
- const confirmed = await cli_1.default.confirm(`Deleting team version ${tag} also deletes every process/module version with that tag, plus the aliases, executions and schedules referencing them. Continue?`);
13
+ const confirmed = await cli_1.default.confirm(`Deleting workspace version ${tag} also deletes every process/module version with that tag, plus the aliases, executions and schedules referencing them. Continue?`);
14
14
  if (!confirmed) {
15
15
  return;
16
16
  }
17
17
  }
18
- cli_1.default.log(color_1.default.cyan(`\n🗑️ deleting team version ${tag}...`));
18
+ cli_1.default.log(color_1.default.cyan(`\n🗑️ deleting workspace version ${tag}...`));
19
19
  await this._fcodeGraphQLClient.deleteTeamVersion(tag);
20
- cli_1.default.log(color_1.default.green(`✅ team version ${tag} deleted`));
20
+ cli_1.default.log(color_1.default.green(`✅ workspace version ${tag} deleted`));
21
21
  // Refresh the local workspace so removed versions/<tag>/ folders disappear.
22
22
  await pull_1.default.run([]);
23
23
  }
24
24
  }
25
- TeamVersionsDeleteCommand.description = 'Delete a team version. CASCADES: every owned process/module version with the tag is deleted, ' +
25
+ SettingsVersionsDeleteCommand.description = 'Delete a workspace version. CASCADES: every owned process/module version with the tag is deleted, ' +
26
26
  'together with the aliases, executions and schedules that reference them';
27
- TeamVersionsDeleteCommand.examples = ['$ fcode team:versions:delete v1.0.0'];
28
- TeamVersionsDeleteCommand.args = {
27
+ SettingsVersionsDeleteCommand.examples = ['$ fcode settings:versions:delete v1.0.0'];
28
+ SettingsVersionsDeleteCommand.args = {
29
29
  tag: core_1.Args.string({ description: 'version tag to delete', required: true }),
30
30
  };
31
- TeamVersionsDeleteCommand.flags = types_1.ForceFlag;
32
- exports.default = TeamVersionsDeleteCommand;
31
+ SettingsVersionsDeleteCommand.flags = types_1.ForceFlag;
32
+ exports.default = SettingsVersionsDeleteCommand;
@@ -1,5 +1,5 @@
1
1
  import ApiClientCommand from '../../../core/commands/api-client-command';
2
- export default class TeamVersionsListCommand extends ApiClientCommand {
2
+ export default class SettingsVersionsListCommand extends ApiClientCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  run(): Promise<void>;
@@ -3,16 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const color_1 = require("@oclif/color");
4
4
  const api_client_command_1 = require("../../../core/commands/api-client-command");
5
5
  const cli_1 = require("../../../utils/cli");
6
- class TeamVersionsListCommand extends api_client_command_1.default {
6
+ class SettingsVersionsListCommand extends api_client_command_1.default {
7
7
  async run() {
8
- await this.parse(TeamVersionsListCommand);
8
+ await this.parse(SettingsVersionsListCommand);
9
9
  const versions = await this._fcodeGraphQLClient.allTeamVersions();
10
10
  if (versions.length === 0) {
11
- cli_1.default.log('📋 No team versions published yet.');
12
- cli_1.default.log(`Publish one with ${color_1.default.cyan('fcode team:versions:create [tag]')}`);
11
+ cli_1.default.log('📋 No workspace versions published yet.');
12
+ cli_1.default.log(`Publish one with ${color_1.default.cyan('fcode settings:versions:create [tag]')}`);
13
13
  return;
14
14
  }
15
- cli_1.default.log('🏷️ Team versions:\n');
15
+ cli_1.default.log('🏷️ Workspace versions:\n');
16
16
  for (const version of versions) {
17
17
  const comment = version.comment ? ` — ${version.comment}` : '';
18
18
  const author = version.createdBy?.name ? ` by ${version.createdBy.name}` : '';
@@ -20,6 +20,6 @@ class TeamVersionsListCommand extends api_client_command_1.default {
20
20
  }
21
21
  }
22
22
  }
23
- TeamVersionsListCommand.description = 'List the team versions published on the active team workspace';
24
- TeamVersionsListCommand.examples = ['$ fcode team:versions:list'];
25
- exports.default = TeamVersionsListCommand;
23
+ SettingsVersionsListCommand.description = 'List the workspace versions published on the active workspace';
24
+ SettingsVersionsListCommand.examples = ['$ fcode settings:versions:list'];
25
+ exports.default = SettingsVersionsListCommand;
@@ -2,5 +2,8 @@ import BaseCommand from '../core/commands/base-command';
2
2
  export default class Status extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
+ static flags: {
6
+ showInherited: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ };
5
8
  run(): Promise<void>;
6
9
  }
@@ -6,19 +6,22 @@ const status_2 = require("./processes/status");
6
6
  const status_3 = require("./variables/status");
7
7
  const status_4 = require("./i18n/status");
8
8
  const status_5 = require("./dependencies/status");
9
- const status_6 = require("./team/status");
9
+ const status_6 = require("./settings/status");
10
+ const types_1 = require("../core/types");
10
11
  class Status extends base_command_1.default {
11
12
  async run() {
12
- await this.parse(Status);
13
+ const { flags: { showInherited }, } = await this.parse(Status);
14
+ const args = showInherited ? ['--showInherited'] : [];
13
15
  await this.logActiveTeam();
14
- await status_2.default.run([]);
15
- await status_1.default.run([]);
16
- await status_3.default.run([]);
17
- await status_4.default.run([]);
16
+ await status_2.default.run(args);
17
+ await status_1.default.run(args);
18
+ await status_3.default.run(args);
19
+ await status_4.default.run(args);
18
20
  await status_5.default.run([]);
19
21
  await status_6.default.run([]);
20
22
  }
21
23
  }
22
24
  Status.description = 'Show team workspace status';
23
- Status.examples = ['$ fcode status'];
25
+ Status.examples = ['$ fcode status', '$ fcode status --showInherited'];
26
+ Status.flags = types_1.ShowInheritedFlag;
24
27
  exports.default = Status;
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class TeamCloneCommand extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ teamId: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ skipSkillsSetup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ init(): Promise<void>;
12
+ run(): Promise<void>;
13
+ private skipSkills;
14
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const fs = require("fs-extra");
5
+ const path = require("node:path");
6
+ const color_1 = require("@oclif/color");
7
+ const dashboard_client_1 = require("../../services/dashboard-client");
8
+ const dev_team_1 = require("../../config/dev-team");
9
+ const global_1 = require("../../config/global");
10
+ const skills_installer_1 = require("../../services/skills-installer");
11
+ const dev_team_service_1 = require("../../services/dev-team-service");
12
+ const settings_1 = require("../../config/settings");
13
+ const slugify_1 = require("../../utils/slugify");
14
+ const cli_1 = require("../../utils/cli");
15
+ class TeamCloneCommand extends core_1.Command {
16
+ async init() {
17
+ const { url } = (0, global_1.parseTeamOrigin)('');
18
+ const { authUrl } = await new global_1.default().getAuthInfo(url);
19
+ await this.config.runHook('auth', { argv: [url, authUrl], id: 'login' });
20
+ }
21
+ async run() {
22
+ const { args, flags } = await this.parse(TeamCloneCommand);
23
+ const cloudUrl = global_1.FCODE_CLOUD_URL;
24
+ const client = new dashboard_client_1.default(cloudUrl);
25
+ const team = await (0, dev_team_service_1.resolveTeam)(client, args.teamId);
26
+ if (!team) {
27
+ return;
28
+ }
29
+ const apps = await client.listTeamApps(team.id);
30
+ if (apps.length === 0) {
31
+ cli_1.default.log(`ℹ️ '${team.name}' has no Apps yet.`);
32
+ return;
33
+ }
34
+ const rootDir = path.resolve((0, slugify_1.slugifyOr)(team.name, team.id));
35
+ if (fs.pathExistsSync(rootDir) && fs.readdirSync(rootDir).length > 0) {
36
+ this.error(`Destination path '${path.basename(rootDir)}' already exists and is not empty.`, {
37
+ code: 'ERR_TEAM_DIR_IS_NOT_EMPTY',
38
+ exit: 1,
39
+ suggestions: [`cd ${path.basename(rootDir)}`, 'fcode team:pull'],
40
+ });
41
+ }
42
+ cli_1.default.log(color_1.default.cyan(`\n🏢 cloning ${apps.length} App(s) of '${team.name}'...`));
43
+ const metadata = {
44
+ developmentTeam: { id: team.id, name: team.name },
45
+ cloudUrl,
46
+ apps: {},
47
+ };
48
+ // Written before anything is cloned, so a run interrupted halfway still leaves a folder
49
+ // `fcode team:pull` recognises and can finish.
50
+ const config = new dev_team_1.default(rootDir);
51
+ config.write(metadata);
52
+ // Skills next: each App links to them as it lands, so an install that fails leaves every
53
+ // App cloned and only the links missing.
54
+ if (!(await this.skipSkills(flags.skipSkillsSetup))) {
55
+ await (0, skills_installer_1.installSkillsAt)(rootDir);
56
+ }
57
+ const plan = (0, dev_team_service_1.planAppSync)(metadata, apps);
58
+ const outcomes = await (0, dev_team_service_1.syncApps)(rootDir, metadata, plan);
59
+ config.write(metadata);
60
+ const failed = (0, dev_team_service_1.reportOutcomes)(outcomes, plan.removed);
61
+ if (failed) {
62
+ process.exitCode = 1;
63
+ cli_1.default.log(`\n⚠️ '${team.name}' cloned with errors — rerun 'fcode team:pull' inside the folder to retry.\n`);
64
+ }
65
+ else {
66
+ cli_1.default.log(`\n🎉 '${team.name}' cloned successfully!\n`);
67
+ }
68
+ cli_1.default.log(`$ cd ${path.basename(rootDir)}\n`);
69
+ }
70
+ async skipSkills(flag) {
71
+ if (flag !== undefined) {
72
+ return flag;
73
+ }
74
+ return (await new global_1.default().getSetting(settings_1.SettingKeys.SKIP_SKILLS_SETUP)) === 'true';
75
+ }
76
+ }
77
+ TeamCloneCommand.description = "Clone every App of a development team into one folder — an App folder each, holding the App's development workspace";
78
+ TeamCloneCommand.examples = ['$ fcode team:clone', '$ fcode team:clone 0f9c1a2b-3d4e-5f60-8a1b-2c3d4e5f6071'];
79
+ TeamCloneCommand.args = {
80
+ teamId: core_1.Args.string({
81
+ description: 'development team id (run without it to list the teams you belong to)',
82
+ name: 'teamId',
83
+ required: false,
84
+ }),
85
+ };
86
+ TeamCloneCommand.flags = {
87
+ skipSkillsSetup: core_1.Flags.boolean({
88
+ description: 'Skip installation of the Factorial Code agent skills',
89
+ }),
90
+ };
91
+ exports.default = TeamCloneCommand;
@@ -1,8 +1,7 @@
1
- import ApiClientCommand from '../../core/commands/api-client-command';
2
- import TeamRepository from '../../repositories/team';
3
- export default class TeamPullCommand extends ApiClientCommand {
1
+ import { Command } from '@oclif/core';
2
+ export default class TeamPullCommand extends Command {
4
3
  static description: string;
5
4
  static examples: string[];
6
- _teamRepository: TeamRepository;
5
+ init(): Promise<void>;
7
6
  run(): Promise<void>;
8
7
  }
@@ -1,30 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
3
4
  const color_1 = require("@oclif/color");
4
- const api_client_command_1 = require("../../core/commands/api-client-command");
5
- const team_1 = require("../../repositories/team");
5
+ const dashboard_client_1 = require("../../services/dashboard-client");
6
+ const dev_team_1 = require("../../config/dev-team");
7
+ const global_1 = require("../../config/global");
8
+ const dev_team_service_1 = require("../../services/dev-team-service");
6
9
  const cli_1 = require("../../utils/cli");
7
- class TeamPullCommand extends api_client_command_1.default {
8
- constructor() {
9
- super(...arguments);
10
- this._teamRepository = new team_1.default();
10
+ class TeamPullCommand extends core_1.Command {
11
+ async init() {
12
+ const { url } = (0, global_1.parseTeamOrigin)('');
13
+ const { authUrl } = await new global_1.default().getAuthInfo(url);
14
+ await this.config.runHook('auth', { argv: [url, authUrl], id: 'login' });
11
15
  }
12
16
  async run() {
13
17
  await this.parse(TeamPullCommand);
14
- cli_1.default.log(color_1.default.cyan('\n🗄️ pulling team settings...'));
15
- const [raw, versions, aliases] = await Promise.all([
16
- this._fcodeGraphQLClient.team(),
17
- this._fcodeGraphQLClient.allTeamVersions(),
18
- this._fcodeGraphQLClient.allTeamVersionAliases(),
19
- ]);
20
- const team = await this._teamRepository.fromRaw(raw);
21
- this._teamRepository.applyCloudVersioning(team, versions, aliases);
22
- this._teamRepository.save(team);
23
- // The stored hash intentionally excludes versions/aliases (see Team.contentHash), so
24
- // pull-only fields never surface as a phantom MODIFIED state.
25
- await this.workspaceConfig.setTeamStoredHash(team.contentHash);
18
+ const rootDir = process.cwd();
19
+ const config = new dev_team_1.default(rootDir);
20
+ const metadata = config.read();
21
+ const { developmentTeam } = metadata;
22
+ cli_1.default.log(color_1.default.cyan(`\n🏢 pulling '${developmentTeam.name}'...`));
23
+ const apps = await new dashboard_client_1.default(metadata.cloudUrl).listTeamApps(developmentTeam.id);
24
+ const plan = (0, dev_team_service_1.planAppSync)(metadata, apps);
25
+ const outcomes = await (0, dev_team_service_1.syncApps)(rootDir, metadata, plan);
26
+ // Written even when an App failed, so the ones that did land are recorded and a rerun
27
+ // only retries what is still missing.
28
+ config.write(metadata);
29
+ if ((0, dev_team_service_1.reportOutcomes)(outcomes, plan.removed)) {
30
+ process.exitCode = 1;
31
+ }
26
32
  }
27
33
  }
28
- TeamPullCommand.description = 'Fetch team settings (timezone, error handler, inheritance) from cloud into team.json';
34
+ TeamPullCommand.description = "Pull every App of the development team folder: clones Apps added since the last run and pulls each App's workspace";
29
35
  TeamPullCommand.examples = ['$ fcode team:pull'];
30
36
  exports.default = TeamPullCommand;
@@ -1,8 +1,7 @@
1
- import ApiClientCommand from '../../core/commands/api-client-command';
2
- import TeamRepository from '../../repositories/team';
3
- export default class TeamStatusCommand extends ApiClientCommand {
1
+ import { Command } from '@oclif/core';
2
+ export default class TeamStatusCommand extends Command {
4
3
  static description: string;
5
4
  static examples: string[];
6
- _teamRepository: TeamRepository;
5
+ init(): Promise<void>;
7
6
  run(): Promise<void>;
8
7
  }
@@ -1,40 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const fs = require("fs-extra");
5
+ const path = require("node:path");
3
6
  const color_1 = require("@oclif/color");
4
- const api_client_command_1 = require("../../core/commands/api-client-command");
5
- const team_1 = require("../../repositories/team");
7
+ const dev_team_1 = require("../../config/dev-team");
8
+ const global_1 = require("../../config/global");
9
+ const status_1 = require("../status");
10
+ const workspace_cloner_1 = require("../../services/workspace-cloner");
6
11
  const cli_1 = require("../../utils/cli");
7
- class TeamStatusCommand extends api_client_command_1.default {
8
- constructor() {
9
- super(...arguments);
10
- this._teamRepository = new team_1.default();
12
+ class TeamStatusCommand extends core_1.Command {
13
+ async init() {
14
+ const { url } = (0, global_1.parseTeamOrigin)('');
15
+ const { authUrl } = await new global_1.default().getAuthInfo(url);
16
+ await this.config.runHook('auth', { argv: [url, authUrl], id: 'login' });
11
17
  }
12
18
  async run() {
13
19
  await this.parse(TeamStatusCommand);
14
- cli_1.default.log(color_1.default.cyan('\nteam status'));
15
- if (!this._teamRepository.exists()) {
16
- cli_1.default.log('No team.json found');
17
- return;
20
+ const rootDir = process.cwd();
21
+ const metadata = new dev_team_1.default(rootDir).read();
22
+ const failed = [];
23
+ for (const app of Object.values(metadata.apps)) {
24
+ cli_1.default.log(color_1.default.cyan(`\n━━ ${app.name} (${app.folder}) ━━`));
25
+ const workspaceDir = path.join(rootDir, app.folder, dev_team_1.APP_WORKSPACE_DIR);
26
+ if (!fs.pathExistsSync(workspaceDir)) {
27
+ cli_1.default.log(color_1.default.yellow('⚠️ not cloned yet — run fcode team:pull'));
28
+ failed.push(app.folder);
29
+ continue;
30
+ }
31
+ try {
32
+ await (0, workspace_cloner_1.runInWorkspace)(workspaceDir, () => status_1.default.run([]));
33
+ }
34
+ catch (error) {
35
+ cli_1.default.log(color_1.default.red(`❌ ${error.message}`));
36
+ failed.push(app.folder);
37
+ }
18
38
  }
19
- const local = await this._teamRepository.find();
20
- const storedHash = await this.workspaceConfig.getTeamStoredHash();
21
- const remote = await this._teamRepository.fromRaw(await this._fcodeGraphQLClient.team());
22
- const localChanged = storedHash !== local.contentHash;
23
- const remoteChanged = storedHash !== remote.contentHash;
24
- if (localChanged && remoteChanged) {
25
- cli_1.default.log(color_1.default.red('🔥 team settings conflict (changed both locally and in the cloud)'));
26
- }
27
- else if (localChanged) {
28
- cli_1.default.log(color_1.default.yellow('🔺 team settings modified locally (run fcode team:push)'));
29
- }
30
- else if (remoteChanged) {
31
- cli_1.default.log(color_1.default.yellow('🔻 team settings out of date (run fcode team:pull)'));
32
- }
33
- else {
34
- cli_1.default.log(color_1.default.green('✅ team settings up to date'));
39
+ if (failed.length > 0) {
40
+ process.exitCode = 1;
41
+ cli_1.default.log(color_1.default.red(`\n${failed.length} App(s) could not be checked: ${failed.join(', ')}`));
35
42
  }
36
43
  }
37
44
  }
38
- TeamStatusCommand.description = 'Show whether team settings (timezone, error handler, inheritance) differ from the cloud';
45
+ TeamStatusCommand.description = "Show what differs from the cloud in every App workspace of the development team folder";
39
46
  TeamStatusCommand.examples = ['$ fcode team:status'];
40
47
  exports.default = TeamStatusCommand;
@@ -0,0 +1,9 @@
1
+ import VariablesCommand from '../../core/commands/variables-command';
2
+ export default class VariablesDiff extends VariablesCommand {
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 variables_command_1 = require("../../core/commands/variables-command");
5
+ class VariablesDiff extends variables_command_1.default {
6
+ async run() {
7
+ if (await this.resourcesDiff()) {
8
+ this.exit(1);
9
+ }
10
+ }
11
+ }
12
+ VariablesDiff.description = 'Show a unified diff between local variables and the cloud. Sensitive values are never printed. Exits 1 when they differ';
13
+ VariablesDiff.examples = ['$ fcode variables:diff', '$ fcode variables:diff MY_VARIABLE'];
14
+ VariablesDiff.args = types_1.ResourceSlugArg;
15
+ exports.default = VariablesDiff;
@@ -6,6 +6,9 @@ export default class VariablesStatus extends VariablesCommand {
6
6
  static args: {
7
7
  resourceSlug: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
8
  };
9
+ static flags: {
10
+ showInherited: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ };
9
12
  resourcesTableLog(resources: Array<MergedResource>): Promise<void>;
10
13
  private logVariableWarnings;
11
14
  run(): Promise<void>;
@@ -66,4 +66,5 @@ class VariablesStatus extends variables_command_1.default {
66
66
  VariablesStatus.description = 'List all variables status';
67
67
  VariablesStatus.examples = ['$ fcode variables:status'];
68
68
  VariablesStatus.args = types_1.ResourceSlugArg;
69
+ VariablesStatus.flags = types_1.ShowInheritedFlag;
69
70
  exports.default = VariablesStatus;
@@ -0,0 +1,48 @@
1
+ import { CLIError } from '../core/errors';
2
+ /**
3
+ * The workspace checkout inside an App folder is always called `app`, never after the slug it was
4
+ * cloned from. The slug is recorded in the metadata, and the directory is only a checkout: pointing
5
+ * it at another workspace with `fcode remote:add` (dev → prod) must not make its name a lie.
6
+ */
7
+ export declare const APP_WORKSPACE_DIR = "app";
8
+ export interface DevTeamApp {
9
+ name: string;
10
+ description: string | null;
11
+ /** Directory holding this App, relative to the team root. Assigned once, then authoritative. */
12
+ folder: string;
13
+ /** Workspace the checkout was cloned from. The directory itself is always `APP_WORKSPACE_DIR`. */
14
+ devWorkspaceSlug: string;
15
+ }
16
+ export interface DevTeamMetadata {
17
+ developmentTeam: {
18
+ id: string;
19
+ name: string;
20
+ };
21
+ cloudUrl: string;
22
+ /** Keyed by App id, so a renamed App keeps its folder. */
23
+ apps: Record<string, DevTeamApp>;
24
+ }
25
+ export declare class NotDevTeamFolder extends CLIError {
26
+ constructor();
27
+ }
28
+ export default class DevTeamConfig {
29
+ private readonly rootDir;
30
+ constructor(rootDir?: string);
31
+ get configPath(): string;
32
+ exists(): boolean;
33
+ read(): DevTeamMetadata;
34
+ write(metadata: DevTeamMetadata): void;
35
+ }
36
+ /** The App metadata committed inside each App folder, so the folder says what it holds. */
37
+ export interface AppSettings {
38
+ id: string;
39
+ name: string;
40
+ description: string | null;
41
+ }
42
+ /**
43
+ * Same name as the workspace settings file one level below, deliberately: each level's
44
+ * `settings.json` describes the thing that level holds. They never collide — this one sits in the
45
+ * App folder, the workspace's inside `APP_WORKSPACE_DIR`.
46
+ */
47
+ export declare const APP_SETTINGS_FILENAME = "settings.json";
48
+ export declare function writeAppSettings(appDir: string, app: AppSettings): void;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APP_SETTINGS_FILENAME = exports.NotDevTeamFolder = exports.APP_WORKSPACE_DIR = void 0;
4
+ exports.writeAppSettings = writeAppSettings;
5
+ const fs = require("fs-extra");
6
+ const path = require("node:path");
7
+ const errors_1 = require("../core/errors");
8
+ /**
9
+ * `.fcode/team.json` at the root of a development-team checkout: what `fcode team:clone` writes
10
+ * and what `team:pull` / `team:status` recognise the folder by.
11
+ *
12
+ * Not to be confused with a workspace's `settings.json` (the file that used to be called
13
+ * `team.json`): this one describes the *development team* — the humans and the Apps they own —
14
+ * and lives one level above the workspaces, inside `.fcode/`.
15
+ */
16
+ const DEV_TEAM_CONFIG_PATH = path.join('.fcode', 'team.json');
17
+ /**
18
+ * The workspace checkout inside an App folder is always called `app`, never after the slug it was
19
+ * cloned from. The slug is recorded in the metadata, and the directory is only a checkout: pointing
20
+ * it at another workspace with `fcode remote:add` (dev → prod) must not make its name a lie.
21
+ */
22
+ exports.APP_WORKSPACE_DIR = 'app';
23
+ class NotDevTeamFolder extends errors_1.CLIError {
24
+ constructor() {
25
+ super('⚠️ Current directory is not a Factorial Code development team folder.', {
26
+ code: 'ERR_NOT_DEV_TEAM_FOLDER',
27
+ exit: 1,
28
+ suggestions: ['fcode team:clone', 'cd <development-team-folder>'],
29
+ });
30
+ }
31
+ }
32
+ exports.NotDevTeamFolder = NotDevTeamFolder;
33
+ class DevTeamConfig {
34
+ constructor(rootDir = process.cwd()) {
35
+ this.rootDir = rootDir;
36
+ }
37
+ get configPath() {
38
+ return path.join(this.rootDir, DEV_TEAM_CONFIG_PATH);
39
+ }
40
+ exists() {
41
+ return fs.pathExistsSync(this.configPath);
42
+ }
43
+ read() {
44
+ if (!this.exists()) {
45
+ throw new NotDevTeamFolder();
46
+ }
47
+ return fs.readJsonSync(this.configPath);
48
+ }
49
+ write(metadata) {
50
+ fs.outputJsonSync(this.configPath, metadata, { spaces: 2 });
51
+ }
52
+ }
53
+ exports.default = DevTeamConfig;
54
+ /**
55
+ * Same name as the workspace settings file one level below, deliberately: each level's
56
+ * `settings.json` describes the thing that level holds. They never collide — this one sits in the
57
+ * App folder, the workspace's inside `APP_WORKSPACE_DIR`.
58
+ */
59
+ exports.APP_SETTINGS_FILENAME = 'settings.json';
60
+ function writeAppSettings(appDir, app) {
61
+ fs.outputJsonSync(path.join(appDir, exports.APP_SETTINGS_FILENAME), app, { spaces: 2 });
62
+ }
@@ -19,8 +19,8 @@ declare class WorkspaceConfig extends Config {
19
19
  exists(resourceType: string, slug: string): Promise<boolean>;
20
20
  getInAnyTeam(resourceType: string, slug: string): Promise<Resource | null>;
21
21
  getStoredHash(resource: Resource): Promise<string>;
22
- getTeamStoredHash(): Promise<string | undefined>;
23
- setTeamStoredHash(contentHash: string): Promise<void>;
22
+ getSettingsStoredHash(): Promise<string | undefined>;
23
+ setSettingsStoredHash(contentHash: string): Promise<void>;
24
24
  add(resourceType: string, resourceMetadata: ResourceMetadata): Promise<void>;
25
25
  remove(resourceType: string, slug: string): Promise<void>;
26
26
  }
@@ -132,10 +132,12 @@ class WorkspaceConfig extends index_1.default {
132
132
  const metadata = (await this.getCollectionByClass(await this.getActiveTeam(), resource))[resource.slug];
133
133
  return metadata?.contentHash;
134
134
  }
135
- async getTeamStoredHash() {
135
+ // The `team` key keeps its name on disk: the hash is computed over the settings content, not the
136
+ // file name, so reusing it keeps every already-cloned workspace from reporting a phantom conflict.
137
+ async getSettingsStoredHash() {
136
138
  return (await this.getActiveTeam()).team?.contentHash;
137
139
  }
138
- async setTeamStoredHash(contentHash) {
140
+ async setSettingsStoredHash(contentHash) {
139
141
  const team = await this.getActiveTeam();
140
142
  await this.setTeam({ ...team, team: { contentHash } });
141
143
  }
@@ -14,6 +14,11 @@ class BaseCommand extends core_1.Command {
14
14
  this.sessionRefreshRetried = false;
15
15
  }
16
16
  async catch(error) {
17
+ // `this.exit(code)` is a control-flow signal, not a failure: swallowing it here would print
18
+ // "EEXIT: 1" and let the process end with a success code. bin/run hands it to oclif's handle().
19
+ if (error instanceof core_1.Errors.ExitError) {
20
+ throw error;
21
+ }
17
22
  // do any extra work with error
18
23
  if (error && error.response && error.response.status === 401) {
19
24
  process.exitCode = 1;
@@ -1,5 +1,6 @@
1
- import { ProgrammingLanguageManifest } from '../types';
1
+ import { ProgrammingLanguage, ProgrammingLanguageManifest, VersionedProgrammingLanguageManifest } from '../types';
2
2
  import ResourceCommand from './resource-command';
3
3
  export default abstract class DependencyCommand extends ResourceCommand<ProgrammingLanguageManifest> {
4
+ _versionedResourceQuery: (programmingLanguage: ProgrammingLanguage) => Promise<Array<VersionedProgrammingLanguageManifest> | null>;
4
5
  init(): Promise<void>;
5
6
  }
@@ -4,6 +4,7 @@ const programming_language_manifest_1 = require("../../repositories/programming-
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 DependencyCommand extends resource_command_1.default {
8
9
  async init() {
9
10
  await super.init();
@@ -11,6 +12,8 @@ class DependencyCommand 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.allProgrammingLanguageManifest.bind(this._fcodeGraphQLClient)();
15
+ this._versionedResourceQuery = (programmingLanguage) => this._fcodeGraphQLClient.allVersionedDependencies.bind(this._fcodeGraphQLClient)(programmingLanguage);
16
+ this._diffFilesFor = resource_differ_1.manifestDiffFiles;
14
17
  }
15
18
  }
16
19
  exports.default = DependencyCommand;
@@ -4,6 +4,7 @@ const locale_1 = require("../../repositories/locale");
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 I18nCommand extends resource_command_1.default {
8
9
  async init() {
9
10
  await super.init();
@@ -11,6 +12,7 @@ class I18nCommand 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.allLocales.bind(this._fcodeGraphQLClient)();
15
+ this._diffFilesFor = resource_differ_1.localeDiffFiles;
14
16
  }
15
17
  }
16
18
  exports.default = I18nCommand;