@factorialco/fcode-cli 1.5.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -3
- package/dist/commands/http.d.ts +6 -4
- package/dist/commands/http.js +40 -24
- package/dist/commands/setup-debug.js +2 -2
- package/dist/commands/team/aliases/delete.d.ts +9 -0
- package/dist/commands/team/aliases/delete.js +28 -0
- package/dist/commands/team/aliases/list.d.ts +6 -0
- package/dist/commands/team/aliases/list.js +23 -0
- package/dist/commands/team/aliases/set.d.ts +10 -0
- package/dist/commands/team/aliases/set.js +25 -0
- package/dist/commands/team/pull.js +8 -1
- package/dist/commands/team/push.js +9 -1
- package/dist/commands/team/versions/create.d.ts +13 -0
- package/dist/commands/team/versions/create.js +42 -0
- package/dist/commands/team/versions/delete.d.ts +12 -0
- package/dist/commands/team/versions/delete.js +32 -0
- package/dist/commands/team/versions/list.d.ts +6 -0
- package/dist/commands/team/versions/list.js +25 -0
- package/dist/commands/variables/status.js +11 -0
- package/dist/config/settings.d.ts +1 -0
- package/dist/config/settings.js +6 -0
- package/dist/core/types.d.ts +65 -4
- package/dist/core/types.js +16 -5
- package/dist/env.d.ts +2 -2
- package/dist/env.js +1 -1
- package/dist/repositories/team.d.ts +10 -2
- package/dist/repositories/team.js +53 -3
- package/dist/repositories/variable.d.ts +24 -0
- package/dist/repositories/variable.js +70 -22
- package/dist/runtime/executor.js +2 -2
- package/dist/runtime/executor.py +2 -2
- package/dist/runtime/libs/python/fcode/__init__.py +2 -2
- package/dist/services/auth-service.d.ts +3 -0
- package/dist/services/auth-service.js +56 -5
- package/dist/services/fcode-graphql-client.d.ts +20 -1
- package/dist/services/fcode-graphql-client.js +239 -49
- package/dist/services/oauth-browser-service.js +1 -0
- package/dist/services/webhook-auth.d.ts +21 -0
- package/dist/services/webhook-auth.js +77 -0
- package/dist/utils/gitignore.d.ts +2 -0
- package/dist/utils/gitignore.js +6 -0
- package/oclif.manifest.json +208 -17
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ $ npm install -g @factorialco/fcode-cli
|
|
|
20
20
|
$ fcode COMMAND
|
|
21
21
|
running command...
|
|
22
22
|
$ fcode (--version)
|
|
23
|
-
@factorialco/fcode-cli/1.
|
|
23
|
+
@factorialco/fcode-cli/2.1.0 linux-x64 node-v22.22.3
|
|
24
24
|
$ fcode --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ fcode COMMAND
|
|
@@ -67,9 +67,15 @@ USAGE
|
|
|
67
67
|
* [`fcode settings:set KEY VALUE`](#fcode-settingsset-key-value)
|
|
68
68
|
* [`fcode setup-debug`](#fcode-setup-debug)
|
|
69
69
|
* [`fcode status`](#fcode-status)
|
|
70
|
+
* [`fcode team:aliases:delete NAME`](#fcode-teamaliasesdelete-name)
|
|
71
|
+
* [`fcode team:aliases:list`](#fcode-teamaliaseslist)
|
|
72
|
+
* [`fcode team:aliases:set NAME TAG`](#fcode-teamaliasesset-name-tag)
|
|
70
73
|
* [`fcode team:pull`](#fcode-teampull)
|
|
71
74
|
* [`fcode team:push`](#fcode-teampush)
|
|
72
75
|
* [`fcode team:status`](#fcode-teamstatus)
|
|
76
|
+
* [`fcode team:versions:create TAG`](#fcode-teamversionscreate-tag)
|
|
77
|
+
* [`fcode team:versions:delete TAG`](#fcode-teamversionsdelete-tag)
|
|
78
|
+
* [`fcode team:versions:list`](#fcode-teamversionslist)
|
|
73
79
|
* [`fcode test [SLUG]`](#fcode-test-slug)
|
|
74
80
|
* [`fcode test:scaffold SLUG`](#fcode-testscaffold-slug)
|
|
75
81
|
* [`fcode variables:add [RESOURCESLUG]`](#fcode-variablesadd-resourceslug)
|
|
@@ -811,6 +817,59 @@ EXAMPLES
|
|
|
811
817
|
$ fcode status
|
|
812
818
|
```
|
|
813
819
|
|
|
820
|
+
## `fcode team:aliases:delete NAME`
|
|
821
|
+
|
|
822
|
+
Delete a team version alias and the per-entity aliases carrying its name on every owned process and module
|
|
823
|
+
|
|
824
|
+
```
|
|
825
|
+
USAGE
|
|
826
|
+
$ fcode team:aliases:delete NAME
|
|
827
|
+
|
|
828
|
+
ARGUMENTS
|
|
829
|
+
NAME alias name to delete
|
|
830
|
+
|
|
831
|
+
DESCRIPTION
|
|
832
|
+
Delete a team version alias and the per-entity aliases carrying its name on every owned process and module
|
|
833
|
+
|
|
834
|
+
EXAMPLES
|
|
835
|
+
$ fcode team:aliases:delete production
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
## `fcode team:aliases:list`
|
|
839
|
+
|
|
840
|
+
List the team version aliases of the active team workspace
|
|
841
|
+
|
|
842
|
+
```
|
|
843
|
+
USAGE
|
|
844
|
+
$ fcode team:aliases:list
|
|
845
|
+
|
|
846
|
+
DESCRIPTION
|
|
847
|
+
List the team version aliases of the active team workspace
|
|
848
|
+
|
|
849
|
+
EXAMPLES
|
|
850
|
+
$ fcode team:aliases:list
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
## `fcode team:aliases:set NAME TAG`
|
|
854
|
+
|
|
855
|
+
Create or re-point a team version alias, upserting the per-entity alias on every owned process and module that has the target tag published
|
|
856
|
+
|
|
857
|
+
```
|
|
858
|
+
USAGE
|
|
859
|
+
$ fcode team:aliases:set NAME TAG
|
|
860
|
+
|
|
861
|
+
ARGUMENTS
|
|
862
|
+
NAME alias name (e.g. production)
|
|
863
|
+
TAG team version tag the alias points at
|
|
864
|
+
|
|
865
|
+
DESCRIPTION
|
|
866
|
+
Create or re-point a team version alias, upserting the per-entity alias on every owned process and module that has the
|
|
867
|
+
target tag published
|
|
868
|
+
|
|
869
|
+
EXAMPLES
|
|
870
|
+
$ fcode team:aliases:set production v1.0.0
|
|
871
|
+
```
|
|
872
|
+
|
|
814
873
|
## `fcode team:pull`
|
|
815
874
|
|
|
816
875
|
Fetch team settings (timezone, error handler, inheritance) from cloud into team.json
|
|
@@ -828,7 +887,7 @@ EXAMPLES
|
|
|
828
887
|
|
|
829
888
|
## `fcode team:push`
|
|
830
889
|
|
|
831
|
-
Update cloud team settings (timezone, error handler, inheritance) from team.json
|
|
890
|
+
Update cloud team settings (timezone, error handler, inheritance, webhook auth) from team.json
|
|
832
891
|
|
|
833
892
|
```
|
|
834
893
|
USAGE
|
|
@@ -838,7 +897,7 @@ FLAGS
|
|
|
838
897
|
-f, --force Force command
|
|
839
898
|
|
|
840
899
|
DESCRIPTION
|
|
841
|
-
Update cloud team settings (timezone, error handler, inheritance) from team.json
|
|
900
|
+
Update cloud team settings (timezone, error handler, inheritance, webhook auth) from team.json
|
|
842
901
|
|
|
843
902
|
EXAMPLES
|
|
844
903
|
$ fcode team:push
|
|
@@ -859,6 +918,67 @@ EXAMPLES
|
|
|
859
918
|
$ fcode team:status
|
|
860
919
|
```
|
|
861
920
|
|
|
921
|
+
## `fcode team:versions:create TAG`
|
|
922
|
+
|
|
923
|
+
Publish a team version: creates a version with the tag on every owned process and module (existing tags are skipped) and pins bare module imports in the published snapshots
|
|
924
|
+
|
|
925
|
+
```
|
|
926
|
+
USAGE
|
|
927
|
+
$ fcode team:versions:create TAG [-c <value>]
|
|
928
|
+
|
|
929
|
+
ARGUMENTS
|
|
930
|
+
TAG version tag to publish (e.g. v1.0.0)
|
|
931
|
+
|
|
932
|
+
FLAGS
|
|
933
|
+
-c, --comment=<value> comment describing the version
|
|
934
|
+
|
|
935
|
+
DESCRIPTION
|
|
936
|
+
Publish a team version: creates a version with the tag on every owned process and module (existing tags are skipped)
|
|
937
|
+
and pins bare module imports in the published snapshots
|
|
938
|
+
|
|
939
|
+
EXAMPLES
|
|
940
|
+
$ fcode team:versions:create v1.0.0
|
|
941
|
+
|
|
942
|
+
$ fcode team:versions:create v1.0.0 --comment "First stable release"
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
## `fcode team:versions:delete TAG`
|
|
946
|
+
|
|
947
|
+
Delete a team version. CASCADES: every owned process/module version with the tag is deleted, together with the aliases, executions and schedules that reference them
|
|
948
|
+
|
|
949
|
+
```
|
|
950
|
+
USAGE
|
|
951
|
+
$ fcode team:versions:delete TAG [-f]
|
|
952
|
+
|
|
953
|
+
ARGUMENTS
|
|
954
|
+
TAG version tag to delete
|
|
955
|
+
|
|
956
|
+
FLAGS
|
|
957
|
+
-f, --force Force command
|
|
958
|
+
|
|
959
|
+
DESCRIPTION
|
|
960
|
+
Delete a team version. CASCADES: every owned process/module version with the tag is deleted, together with the
|
|
961
|
+
aliases, executions and schedules that reference them
|
|
962
|
+
|
|
963
|
+
EXAMPLES
|
|
964
|
+
$ fcode team:versions:delete v1.0.0
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
## `fcode team:versions:list`
|
|
968
|
+
|
|
969
|
+
List the team versions published on the active team workspace
|
|
970
|
+
|
|
971
|
+
```
|
|
972
|
+
USAGE
|
|
973
|
+
$ fcode team:versions:list
|
|
974
|
+
|
|
975
|
+
DESCRIPTION
|
|
976
|
+
List the team versions published on the active team workspace
|
|
977
|
+
|
|
978
|
+
EXAMPLES
|
|
979
|
+
$ fcode team:versions:list
|
|
980
|
+
```
|
|
981
|
+
|
|
862
982
|
## `fcode test [SLUG]`
|
|
863
983
|
|
|
864
984
|
Run tests for Factorial Code processes
|
package/dist/commands/http.d.ts
CHANGED
|
@@ -11,12 +11,14 @@ export default class Http extends Command {
|
|
|
11
11
|
};
|
|
12
12
|
run(): Promise<void>;
|
|
13
13
|
/**
|
|
14
|
-
* Enforces the
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
|
|
15
|
+
* configuration names a variable, whose value is looked up in the workspace variables and compared
|
|
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.
|
|
18
|
+
* Returns true when the request may proceed, false after writing a 403.
|
|
18
19
|
*/
|
|
19
20
|
private _checkWebhookAuth;
|
|
21
|
+
private _forbid;
|
|
20
22
|
private _handleProcessExecution;
|
|
21
23
|
private _sendResponse;
|
|
22
24
|
}
|
package/dist/commands/http.js
CHANGED
|
@@ -7,6 +7,9 @@ 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");
|
|
11
|
+
const webhook_auth_1 = require("../services/webhook-auth");
|
|
12
|
+
const types_1 = require("../core/types");
|
|
10
13
|
class Http extends core_1.Command {
|
|
11
14
|
async run() {
|
|
12
15
|
const { flags: { port, logLevel, 'auth-user': authUser, 'auth-password': authPassword, jsonLogs }, } = await this.parse(Http);
|
|
@@ -16,11 +19,17 @@ class Http extends core_1.Command {
|
|
|
16
19
|
: null;
|
|
17
20
|
const { slug: teamSlug } = await new workspace_1.default().getTeamOrigin();
|
|
18
21
|
const processRepository = new process_1.default();
|
|
22
|
+
const teamRepository = new team_1.default();
|
|
23
|
+
if (useBasicAuth) {
|
|
24
|
+
this.warn(`--auth-user/--auth-password protect the whole server through the ${types_1.DEFAULT_WEBHOOK_AUTH_HEADER} header, ` +
|
|
25
|
+
`so a webhook expecting its credential there cannot be exercised at the same time.`);
|
|
26
|
+
}
|
|
19
27
|
const handler = async (req, res) => {
|
|
20
28
|
// CORS headers
|
|
21
29
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
22
30
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
23
|
-
|
|
31
|
+
// Reflected rather than fixed, so a webhook using a custom auth header passes the preflight.
|
|
32
|
+
res.setHeader('Access-Control-Allow-Headers', req.headers['access-control-request-headers'] ?? 'Content-Type, Authorization, Fcode-Async');
|
|
24
33
|
if (req.method === 'OPTIONS') {
|
|
25
34
|
res.writeHead(204);
|
|
26
35
|
res.end();
|
|
@@ -77,7 +86,7 @@ class Http extends core_1.Command {
|
|
|
77
86
|
res.end(JSON.stringify({ success: false, error: `Process slug required in path (e.g. /api/${teamSlug}/webhooks/my-process)` }));
|
|
78
87
|
return;
|
|
79
88
|
}
|
|
80
|
-
if (!(await this._checkWebhookAuth(req, res, processRepository, slug))) {
|
|
89
|
+
if (!(await this._checkWebhookAuth(req, res, processRepository, teamRepository, slug))) {
|
|
81
90
|
return;
|
|
82
91
|
}
|
|
83
92
|
await this._handleProcessExecution(req, res, slug, urlObj, logLevel, jsonLogs);
|
|
@@ -110,12 +119,13 @@ class Http extends core_1.Command {
|
|
|
110
119
|
await new Promise(() => { });
|
|
111
120
|
}
|
|
112
121
|
/**
|
|
113
|
-
* Enforces the
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
122
|
+
* Enforces the webhook's configured auth, simulating the cloud webhook endpoint: the effective
|
|
123
|
+
* configuration names a variable, whose value is looked up in the workspace variables and compared
|
|
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.
|
|
126
|
+
* Returns true when the request may proceed, false after writing a 403.
|
|
117
127
|
*/
|
|
118
|
-
async _checkWebhookAuth(req, res, processRepository, slug) {
|
|
128
|
+
async _checkWebhookAuth(req, res, processRepository, teamRepository, slug) {
|
|
119
129
|
let webhook;
|
|
120
130
|
try {
|
|
121
131
|
webhook = (await processRepository.find(slug)).webhook;
|
|
@@ -124,28 +134,34 @@ class Http extends core_1.Command {
|
|
|
124
134
|
// Unknown process: let the execution path report the 404.
|
|
125
135
|
return true;
|
|
126
136
|
}
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
// Read per request, like the process metadata above, so editing team.json while the server
|
|
138
|
+
// runs takes effect without a restart.
|
|
139
|
+
const teamWebhookAuth = (await teamRepository.find()).webhookAuth;
|
|
140
|
+
let effectiveAuth;
|
|
141
|
+
try {
|
|
142
|
+
effectiveAuth = (0, webhook_auth_1.resolveEffectiveAuth)(webhook, teamWebhookAuth);
|
|
129
143
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
144
|
-
res.end(JSON.stringify({ success: false, error: 'Unauthorized' }));
|
|
144
|
+
catch (error) {
|
|
145
|
+
// Fails closed, and says why: otherwise this reads as a wrong token rather than a local
|
|
146
|
+
// configuration the developer still has to complete.
|
|
147
|
+
this.warn(`Rejecting the ${slug} webhook: ${error.message}`);
|
|
148
|
+
this._forbid(res);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const variables = effectiveAuth === null ? [] : await new variable_1.default().findAllForExecution();
|
|
152
|
+
const variableValue = variables.find((variable) => variable.key === effectiveAuth?.variableKey)?.value;
|
|
153
|
+
const result = (0, webhook_auth_1.checkWebhookAuth)(effectiveAuth, req.headers, variableValue);
|
|
154
|
+
if (!result.authorized) {
|
|
155
|
+
this.warn(`Rejecting the ${slug} webhook: ${result.reason}`);
|
|
156
|
+
this._forbid(res);
|
|
145
157
|
return false;
|
|
146
158
|
}
|
|
147
159
|
return true;
|
|
148
160
|
}
|
|
161
|
+
_forbid(res) {
|
|
162
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
163
|
+
res.end(JSON.stringify({ success: false, error: 'Forbidden' }));
|
|
164
|
+
}
|
|
149
165
|
async _handleProcessExecution(req, res, slug, urlObj, logLevel, jsonLogs) {
|
|
150
166
|
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
151
167
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
@@ -70,11 +70,11 @@ class SetupDebug extends core_1.Command {
|
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
if (process.env.
|
|
73
|
+
if (process.env.FC_COMMAND) {
|
|
74
74
|
configurations.forEach((configuration) => {
|
|
75
75
|
configuration.env = {
|
|
76
76
|
...configuration.env,
|
|
77
|
-
|
|
77
|
+
FC_COMMAND: process.env.FC_COMMAND
|
|
78
78
|
};
|
|
79
79
|
});
|
|
80
80
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ApiClientCommand from '../../../core/commands/api-client-command';
|
|
2
|
+
export default class TeamAliasesDeleteCommand extends ApiClientCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const color_1 = require("@oclif/color");
|
|
5
|
+
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
6
|
+
const pull_1 = require("../pull");
|
|
7
|
+
const cli_1 = require("../../../utils/cli");
|
|
8
|
+
class TeamAliasesDeleteCommand extends api_client_command_1.default {
|
|
9
|
+
async run() {
|
|
10
|
+
const { args: { name }, } = await this.parse(TeamAliasesDeleteCommand);
|
|
11
|
+
const aliases = await this._fcodeGraphQLClient.allTeamVersionAliases();
|
|
12
|
+
const alias = aliases.find((candidate) => candidate.name === name);
|
|
13
|
+
if (!alias) {
|
|
14
|
+
cli_1.default.error(`Alias ${name} does not exist`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
cli_1.default.log(color_1.default.cyan(`\n🗑️ deleting alias ${name}...`));
|
|
18
|
+
await this._fcodeGraphQLClient.deleteTeamVersionAlias(alias.id);
|
|
19
|
+
cli_1.default.log(color_1.default.green(`✅ alias ${name} deleted`));
|
|
20
|
+
await pull_1.default.run([]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
TeamAliasesDeleteCommand.description = 'Delete a team version alias and the per-entity aliases carrying its name on every owned process and module';
|
|
24
|
+
TeamAliasesDeleteCommand.examples = ['$ fcode team:aliases:delete production'];
|
|
25
|
+
TeamAliasesDeleteCommand.args = {
|
|
26
|
+
name: core_1.Args.string({ description: 'alias name to delete', required: true }),
|
|
27
|
+
};
|
|
28
|
+
exports.default = TeamAliasesDeleteCommand;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const color_1 = require("@oclif/color");
|
|
4
|
+
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
5
|
+
const cli_1 = require("../../../utils/cli");
|
|
6
|
+
class TeamAliasesListCommand extends api_client_command_1.default {
|
|
7
|
+
async run() {
|
|
8
|
+
await this.parse(TeamAliasesListCommand);
|
|
9
|
+
const aliases = await this._fcodeGraphQLClient.allTeamVersionAliases();
|
|
10
|
+
if (aliases.length === 0) {
|
|
11
|
+
cli_1.default.log('📋 No team version aliases yet.');
|
|
12
|
+
cli_1.default.log(`Create one with ${color_1.default.cyan('fcode team:aliases:set [name] [tag]')}`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
cli_1.default.log('🔗 Team version aliases:\n');
|
|
16
|
+
for (const alias of aliases) {
|
|
17
|
+
cli_1.default.log(` ${color_1.default.cyan(alias.name)} → ${alias.tag}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
TeamAliasesListCommand.description = 'List the team version aliases of the active team workspace';
|
|
22
|
+
TeamAliasesListCommand.examples = ['$ fcode team:aliases:list'];
|
|
23
|
+
exports.default = TeamAliasesListCommand;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import ApiClientCommand from '../../../core/commands/api-client-command';
|
|
2
|
+
export default class TeamAliasesSetCommand extends ApiClientCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
name: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
tag: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const color_1 = require("@oclif/color");
|
|
5
|
+
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
6
|
+
const pull_1 = require("../pull");
|
|
7
|
+
const cli_1 = require("../../../utils/cli");
|
|
8
|
+
class TeamAliasesSetCommand extends api_client_command_1.default {
|
|
9
|
+
async run() {
|
|
10
|
+
const { args: { name, tag }, } = await this.parse(TeamAliasesSetCommand);
|
|
11
|
+
cli_1.default.log(color_1.default.cyan(`\n🔗 pointing alias ${name} at ${tag}...`));
|
|
12
|
+
await this._fcodeGraphQLClient.createTeamVersionAlias(name, tag);
|
|
13
|
+
cli_1.default.log(color_1.default.green(`✅ alias ${name} → ${tag}`));
|
|
14
|
+
// Aliases have no folder representation; refreshing team.json is enough.
|
|
15
|
+
await pull_1.default.run([]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
TeamAliasesSetCommand.description = 'Create or re-point a team version alias, upserting the per-entity alias on every owned ' +
|
|
19
|
+
'process and module that has the target tag published';
|
|
20
|
+
TeamAliasesSetCommand.examples = ['$ fcode team:aliases:set production v1.0.0'];
|
|
21
|
+
TeamAliasesSetCommand.args = {
|
|
22
|
+
name: core_1.Args.string({ description: 'alias name (e.g. production)', required: true }),
|
|
23
|
+
tag: core_1.Args.string({ description: 'team version tag the alias points at', required: true }),
|
|
24
|
+
};
|
|
25
|
+
exports.default = TeamAliasesSetCommand;
|
|
@@ -12,9 +12,16 @@ class TeamPullCommand extends api_client_command_1.default {
|
|
|
12
12
|
async run() {
|
|
13
13
|
await this.parse(TeamPullCommand);
|
|
14
14
|
cli_1.default.log(color_1.default.cyan('\n🗄️ pulling team settings...'));
|
|
15
|
-
const raw = await
|
|
15
|
+
const [raw, versions, aliases] = await Promise.all([
|
|
16
|
+
this._fcodeGraphQLClient.team(),
|
|
17
|
+
this._fcodeGraphQLClient.allTeamVersions(),
|
|
18
|
+
this._fcodeGraphQLClient.allTeamVersionAliases(),
|
|
19
|
+
]);
|
|
16
20
|
const team = await this._teamRepository.fromRaw(raw);
|
|
21
|
+
this._teamRepository.applyCloudVersioning(team, versions, aliases);
|
|
17
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.
|
|
18
25
|
await this.workspaceConfig.setTeamStoredHash(team.contentHash);
|
|
19
26
|
}
|
|
20
27
|
}
|
|
@@ -22,6 +22,9 @@ class TeamPushCommand extends api_client_command_1.default {
|
|
|
22
22
|
cli_1.default.log('Team settings already up to date');
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
+
// team.json's `versions`/`aliases` fields are pull-only and intentionally left out of the
|
|
26
|
+
// update payload: team versions and aliases are only written through the explicit
|
|
27
|
+
// team:versions/team:aliases commands.
|
|
25
28
|
const update = { parentTeamSlugs: team.parentTeamSlugs };
|
|
26
29
|
if (team.zoneId !== undefined) {
|
|
27
30
|
update.zoneId = team.zoneId;
|
|
@@ -30,12 +33,17 @@ class TeamPushCommand extends api_client_command_1.default {
|
|
|
30
33
|
const processId = await this._teamRepository.resolveSlugToId(team.errorHandlerConfig.processSlug);
|
|
31
34
|
update.executionErrorHandlerConfig = { processId, tag: team.errorHandlerConfig.tag ?? null };
|
|
32
35
|
}
|
|
36
|
+
// Sent unconditionally, null arm included: team.json is the source of truth, so removing
|
|
37
|
+
// webhookAuth from it must clear the cloud configuration rather than leave it dangling.
|
|
38
|
+
update.webhookAuth = team.webhookAuth
|
|
39
|
+
? { headerName: team.webhookAuth.headerName ?? null, variableKey: team.webhookAuth.variableKey }
|
|
40
|
+
: { variableKey: null };
|
|
33
41
|
await this._fcodeGraphQLClient.pushTeam(update);
|
|
34
42
|
await this.workspaceConfig.setTeamStoredHash(team.contentHash);
|
|
35
43
|
cli_1.default.log(color_1.default.green('✅ team settings pushed'));
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
|
-
TeamPushCommand.description = 'Update cloud team settings (timezone, error handler, inheritance) from team.json';
|
|
46
|
+
TeamPushCommand.description = 'Update cloud team settings (timezone, error handler, inheritance, webhook auth) from team.json';
|
|
39
47
|
TeamPushCommand.examples = ['$ fcode team:push'];
|
|
40
48
|
TeamPushCommand.flags = types_1.ForceFlag;
|
|
41
49
|
exports.default = TeamPushCommand;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ApiClientCommand from '../../../core/commands/api-client-command';
|
|
2
|
+
export default class TeamVersionsCreateCommand extends ApiClientCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
tag: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
comment: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private logManifest;
|
|
13
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const color_1 = require("@oclif/color");
|
|
5
|
+
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
6
|
+
const pull_1 = require("../../pull");
|
|
7
|
+
const cli_1 = require("../../../utils/cli");
|
|
8
|
+
class TeamVersionsCreateCommand extends api_client_command_1.default {
|
|
9
|
+
async run() {
|
|
10
|
+
const { args: { tag }, flags: { comment }, } = await this.parse(TeamVersionsCreateCommand);
|
|
11
|
+
cli_1.default.log(color_1.default.cyan(`\n🏷️ publishing team version ${tag}...`));
|
|
12
|
+
const teamVersion = await this._fcodeGraphQLClient.createTeamVersion(tag, comment);
|
|
13
|
+
const failedCount = this.logManifest(teamVersion);
|
|
14
|
+
// Refresh the local workspace: per-entity versions/<tag>/ folders and team.json.
|
|
15
|
+
await pull_1.default.run([]);
|
|
16
|
+
// Release pipelines must be able to detect an incomplete version. Re-running the
|
|
17
|
+
// command converges: it publishes only what is still missing.
|
|
18
|
+
if (failedCount > 0) {
|
|
19
|
+
this.error(`team version ${tag} published with ${failedCount} failed entities`, { exit: 1 });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
logManifest(teamVersion) {
|
|
23
|
+
const created = teamVersion.manifest.filter(({ outcome }) => outcome === 'CREATED');
|
|
24
|
+
const skipped = teamVersion.manifest.filter(({ outcome }) => outcome === 'SKIPPED_EXISTS');
|
|
25
|
+
const failed = teamVersion.manifest.filter(({ outcome }) => outcome === 'FAILED');
|
|
26
|
+
cli_1.default.log(color_1.default.green(`✅ ${created.length} created`) + `, ${skipped.length} skipped (tag already exists)` + (failed.length > 0 ? `, ${color_1.default.red(`${failed.length} failed`)}` : ''));
|
|
27
|
+
for (const entry of failed) {
|
|
28
|
+
cli_1.default.error(`${entry.resourceType.toLowerCase()} ${entry.slug} — ${entry.error ?? 'unknown error'}`);
|
|
29
|
+
}
|
|
30
|
+
return failed.length;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
TeamVersionsCreateCommand.description = 'Publish a team version: creates a version with the tag on every owned process and module ' +
|
|
34
|
+
'(existing tags are skipped) and pins bare module imports in the published snapshots';
|
|
35
|
+
TeamVersionsCreateCommand.examples = ['$ fcode team:versions:create v1.0.0', '$ fcode team:versions:create v1.0.0 --comment "First stable release"'];
|
|
36
|
+
TeamVersionsCreateCommand.args = {
|
|
37
|
+
tag: core_1.Args.string({ description: 'version tag to publish (e.g. v1.0.0)', required: true }),
|
|
38
|
+
};
|
|
39
|
+
TeamVersionsCreateCommand.flags = {
|
|
40
|
+
comment: core_1.Flags.string({ char: 'c', description: 'comment describing the version' }),
|
|
41
|
+
};
|
|
42
|
+
exports.default = TeamVersionsCreateCommand;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ApiClientCommand from '../../../core/commands/api-client-command';
|
|
2
|
+
export default class TeamVersionsDeleteCommand extends ApiClientCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
tag: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const color_1 = require("@oclif/color");
|
|
5
|
+
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
6
|
+
const pull_1 = require("../../pull");
|
|
7
|
+
const types_1 = require("../../../core/types");
|
|
8
|
+
const cli_1 = require("../../../utils/cli");
|
|
9
|
+
class TeamVersionsDeleteCommand extends api_client_command_1.default {
|
|
10
|
+
async run() {
|
|
11
|
+
const { args: { tag }, flags: { force }, } = await this.parse(TeamVersionsDeleteCommand);
|
|
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?`);
|
|
14
|
+
if (!confirmed) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
cli_1.default.log(color_1.default.cyan(`\n🗑️ deleting team version ${tag}...`));
|
|
19
|
+
await this._fcodeGraphQLClient.deleteTeamVersion(tag);
|
|
20
|
+
cli_1.default.log(color_1.default.green(`✅ team version ${tag} deleted`));
|
|
21
|
+
// Refresh the local workspace so removed versions/<tag>/ folders disappear.
|
|
22
|
+
await pull_1.default.run([]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
TeamVersionsDeleteCommand.description = 'Delete a team version. CASCADES: every owned process/module version with the tag is deleted, ' +
|
|
26
|
+
'together with the aliases, executions and schedules that reference them';
|
|
27
|
+
TeamVersionsDeleteCommand.examples = ['$ fcode team:versions:delete v1.0.0'];
|
|
28
|
+
TeamVersionsDeleteCommand.args = {
|
|
29
|
+
tag: core_1.Args.string({ description: 'version tag to delete', required: true }),
|
|
30
|
+
};
|
|
31
|
+
TeamVersionsDeleteCommand.flags = types_1.ForceFlag;
|
|
32
|
+
exports.default = TeamVersionsDeleteCommand;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const color_1 = require("@oclif/color");
|
|
4
|
+
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
5
|
+
const cli_1 = require("../../../utils/cli");
|
|
6
|
+
class TeamVersionsListCommand extends api_client_command_1.default {
|
|
7
|
+
async run() {
|
|
8
|
+
await this.parse(TeamVersionsListCommand);
|
|
9
|
+
const versions = await this._fcodeGraphQLClient.allTeamVersions();
|
|
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]')}`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
cli_1.default.log('🏷️ Team versions:\n');
|
|
16
|
+
for (const version of versions) {
|
|
17
|
+
const comment = version.comment ? ` — ${version.comment}` : '';
|
|
18
|
+
const author = version.createdBy?.name ? ` by ${version.createdBy.name}` : '';
|
|
19
|
+
cli_1.default.log(` ${color_1.default.cyan(version.tag)}${comment} (${version.createdAt}${author})`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
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;
|
|
@@ -11,6 +11,7 @@ class VariablesStatus extends variables_command_1.default {
|
|
|
11
11
|
async resourcesTableLog(resources) {
|
|
12
12
|
const globalConfig = new global_1.default();
|
|
13
13
|
const localVariablesFileName = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
|
|
14
|
+
const hasInherited = resources.some((resource) => resource.resolvingTeamSlug);
|
|
14
15
|
cli_1.cli.table(resources, {
|
|
15
16
|
key: {
|
|
16
17
|
get({ key, status }) {
|
|
@@ -18,6 +19,16 @@ class VariablesStatus extends variables_command_1.default {
|
|
|
18
19
|
},
|
|
19
20
|
header: 'key',
|
|
20
21
|
},
|
|
22
|
+
...(hasInherited
|
|
23
|
+
? {
|
|
24
|
+
inherited: {
|
|
25
|
+
get(resource) {
|
|
26
|
+
return (0, colorize_resource_1.colorizeByStatus)(resource.resolvingTeamSlug ? `🔗 ${resource.resolvingTeamSlug}` : '', resource.status);
|
|
27
|
+
},
|
|
28
|
+
header: 'inherited',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
: {}),
|
|
21
32
|
status: {
|
|
22
33
|
get({ status }) {
|
|
23
34
|
return (0, colorize_resource_1.colorizeByStatus)(status, status);
|
|
@@ -8,6 +8,7 @@ export declare enum SettingKeys {
|
|
|
8
8
|
IGNORE_PROCESS_VERSIONS = "ignoreProcessVersions",
|
|
9
9
|
IGNORE_WORKSPACE_NOT_EMPTY = "ignoreWorkspaceNotEmpty",
|
|
10
10
|
IGNORE_FCODE_RUN_PROCESSES = "ignoreFcodeRunProcesses",
|
|
11
|
+
INHERITED_VARIABLES_FILE_NAME = "inheritedVariablesFileName",
|
|
11
12
|
LOCAL_VARIABLES_FILE_NAME = "localVariablesFileName",
|
|
12
13
|
SKIP_SKILLS_SETUP = "skipSkillsSetup",
|
|
13
14
|
VARIABLES_FILE_NAME = "variablesFileName"
|