@factorialco/fcode-cli 2.3.0 → 2.4.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 +12 -8
- package/dist/commands/dependencies/status.d.ts +11 -0
- package/dist/commands/dependencies/status.js +43 -0
- package/dist/commands/pull.js +1 -1
- package/dist/commands/team/pull.d.ts +3 -0
- package/dist/commands/team/pull.js +15 -4
- package/dist/commands/team/push.d.ts +2 -0
- package/dist/commands/team/push.js +55 -4
- package/dist/commands/team/status.js +8 -2
- package/dist/commands/team/versions/create.d.ts +0 -1
- package/dist/commands/team/versions/create.js +2 -11
- package/dist/core/dependencies/env-dependencies-command.js +6 -3
- package/dist/core/executors/javascript-execution-task.js +14 -5
- package/dist/core/types.d.ts +29 -2
- package/dist/core/types.js +27 -1
- package/dist/core/utils/dependencies.js +4 -1
- package/dist/env.js +1 -1
- package/dist/repositories/process.js +4 -0
- package/dist/repositories/programming-language-manifest.d.ts +17 -1
- package/dist/repositories/programming-language-manifest.js +51 -1
- package/dist/repositories/team.d.ts +13 -3
- package/dist/repositories/team.js +51 -8
- package/dist/runtime/libs/python/fcode/context.py +15 -8
- package/dist/services/fcode-graphql-client.d.ts +1 -0
- package/dist/services/fcode-graphql-client.js +71 -8
- package/dist/services/locale-resolver.d.ts +6 -1
- package/dist/services/locale-resolver.js +11 -4
- package/dist/services/team-versioning.d.ts +16 -0
- package/dist/services/team-versioning.js +40 -0
- package/dist/utils/gitignore.d.ts +3 -1
- package/dist/utils/gitignore.js +11 -2
- package/oclif.manifest.json +13 -5
- package/package.json +1 -1
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/2.
|
|
23
|
+
@factorialco/fcode-cli/2.4.0 linux-x64 node-v22.22.3
|
|
24
24
|
$ fcode --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ fcode COMMAND
|
|
@@ -998,14 +998,17 @@ EXAMPLES
|
|
|
998
998
|
|
|
999
999
|
## `fcode team:pull`
|
|
1000
1000
|
|
|
1001
|
-
Fetch team settings (timezone, error handler, inheritance) from cloud into team.json
|
|
1001
|
+
Fetch team settings (timezone, error handler, inheritance) and team versions/aliases from cloud into team.json
|
|
1002
1002
|
|
|
1003
1003
|
```
|
|
1004
1004
|
USAGE
|
|
1005
|
-
$ fcode team:pull
|
|
1005
|
+
$ fcode team:pull [-f]
|
|
1006
|
+
|
|
1007
|
+
FLAGS
|
|
1008
|
+
-f, --force Force command
|
|
1006
1009
|
|
|
1007
1010
|
DESCRIPTION
|
|
1008
|
-
Fetch team settings (timezone, error handler, inheritance) from cloud into team.json
|
|
1011
|
+
Fetch team settings (timezone, error handler, inheritance) and team versions/aliases from cloud into team.json
|
|
1009
1012
|
|
|
1010
1013
|
EXAMPLES
|
|
1011
1014
|
$ fcode team:pull
|
|
@@ -1013,7 +1016,7 @@ EXAMPLES
|
|
|
1013
1016
|
|
|
1014
1017
|
## `fcode team:push`
|
|
1015
1018
|
|
|
1016
|
-
Update cloud team settings (timezone, error handler, inheritance, webhook auth) from team.json
|
|
1019
|
+
Update cloud team settings (timezone, error handler, inheritance, webhook auth) and sync team versions/aliases from team.json
|
|
1017
1020
|
|
|
1018
1021
|
```
|
|
1019
1022
|
USAGE
|
|
@@ -1023,7 +1026,8 @@ FLAGS
|
|
|
1023
1026
|
-f, --force Force command
|
|
1024
1027
|
|
|
1025
1028
|
DESCRIPTION
|
|
1026
|
-
Update cloud team settings (timezone, error handler, inheritance, webhook auth)
|
|
1029
|
+
Update cloud team settings (timezone, error handler, inheritance, webhook auth) and sync team versions/aliases from
|
|
1030
|
+
team.json
|
|
1027
1031
|
|
|
1028
1032
|
EXAMPLES
|
|
1029
1033
|
$ fcode team:push
|
|
@@ -1031,14 +1035,14 @@ EXAMPLES
|
|
|
1031
1035
|
|
|
1032
1036
|
## `fcode team:status`
|
|
1033
1037
|
|
|
1034
|
-
Show whether team settings (timezone, error handler, inheritance) differ from the cloud
|
|
1038
|
+
Show whether team settings (timezone, error handler, inheritance) or team versions/aliases differ from the cloud
|
|
1035
1039
|
|
|
1036
1040
|
```
|
|
1037
1041
|
USAGE
|
|
1038
1042
|
$ fcode team:status
|
|
1039
1043
|
|
|
1040
1044
|
DESCRIPTION
|
|
1041
|
-
Show whether team settings (timezone, error handler, inheritance) differ from the cloud
|
|
1045
|
+
Show whether team settings (timezone, error handler, inheritance) or team versions/aliases differ from the cloud
|
|
1042
1046
|
|
|
1043
1047
|
EXAMPLES
|
|
1044
1048
|
$ fcode team:status
|
|
@@ -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
|
}
|
package/dist/commands/pull.js
CHANGED
|
@@ -23,7 +23,7 @@ class PullCommand extends base_command_1.default {
|
|
|
23
23
|
await pull_5.default.run(args);
|
|
24
24
|
// Team settings are pulled last so process metadata is available to resolve the
|
|
25
25
|
// error-handler process id to its slug.
|
|
26
|
-
await pull_6.default.run(
|
|
26
|
+
await pull_6.default.run(args);
|
|
27
27
|
// Keep inherited resources out of git now that all resource metadata is saved.
|
|
28
28
|
await (0, gitignore_1.syncInheritedGitignore)();
|
|
29
29
|
}
|
|
@@ -3,6 +3,9 @@ import TeamRepository from '../../repositories/team';
|
|
|
3
3
|
export default class TeamPullCommand extends ApiClientCommand {
|
|
4
4
|
static description: string;
|
|
5
5
|
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
6
9
|
_teamRepository: TeamRepository;
|
|
7
10
|
run(): Promise<void>;
|
|
8
11
|
}
|
|
@@ -3,6 +3,7 @@ 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 team_1 = require("../../repositories/team");
|
|
6
|
+
const types_1 = require("../../core/types");
|
|
6
7
|
const cli_1 = require("../../utils/cli");
|
|
7
8
|
class TeamPullCommand extends api_client_command_1.default {
|
|
8
9
|
constructor() {
|
|
@@ -10,8 +11,19 @@ class TeamPullCommand extends api_client_command_1.default {
|
|
|
10
11
|
this._teamRepository = new team_1.default();
|
|
11
12
|
}
|
|
12
13
|
async run() {
|
|
13
|
-
await this.parse(TeamPullCommand);
|
|
14
|
+
const { flags: { force }, } = await this.parse(TeamPullCommand);
|
|
14
15
|
cli_1.default.log(color_1.default.cyan('\n🗄️ pulling team settings...'));
|
|
16
|
+
// Local edits (a version tag added, an alias re-pointed) would be silently wiped by the
|
|
17
|
+
// overwrite below, so an unpushed team.json blocks the pull. No stored hash means there is
|
|
18
|
+
// no baseline to have diverged from (fresh workspace), so the pull proceeds.
|
|
19
|
+
if (!force && this._teamRepository.exists()) {
|
|
20
|
+
const local = await this._teamRepository.find();
|
|
21
|
+
const storedHash = await this.workspaceConfig.getTeamStoredHash();
|
|
22
|
+
if (storedHash !== undefined && storedHash !== local.contentHash) {
|
|
23
|
+
cli_1.default.log(color_1.default.yellow('🔺 team.json has unpushed local changes — run fcode team:push first, or fcode team:pull --force to overwrite them'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
const [raw, versions, aliases] = await Promise.all([
|
|
16
28
|
this._fcodeGraphQLClient.team(),
|
|
17
29
|
this._fcodeGraphQLClient.allTeamVersions(),
|
|
@@ -20,11 +32,10 @@ class TeamPullCommand extends api_client_command_1.default {
|
|
|
20
32
|
const team = await this._teamRepository.fromRaw(raw);
|
|
21
33
|
this._teamRepository.applyCloudVersioning(team, versions, aliases);
|
|
22
34
|
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
35
|
await this.workspaceConfig.setTeamStoredHash(team.contentHash);
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
|
-
TeamPullCommand.description = 'Fetch team settings (timezone, error handler, inheritance) from cloud into team.json';
|
|
38
|
+
TeamPullCommand.description = 'Fetch team settings (timezone, error handler, inheritance) and team versions/aliases from cloud into team.json';
|
|
29
39
|
TeamPullCommand.examples = ['$ fcode team:pull'];
|
|
40
|
+
TeamPullCommand.flags = types_1.ForceFlag;
|
|
30
41
|
exports.default = TeamPullCommand;
|
|
@@ -4,6 +4,7 @@ const color_1 = require("@oclif/color");
|
|
|
4
4
|
const api_client_command_1 = require("../../core/commands/api-client-command");
|
|
5
5
|
const team_1 = require("../../repositories/team");
|
|
6
6
|
const types_1 = require("../../core/types");
|
|
7
|
+
const team_versioning_1 = require("../../services/team-versioning");
|
|
7
8
|
const cli_1 = require("../../utils/cli");
|
|
8
9
|
class TeamPushCommand extends api_client_command_1.default {
|
|
9
10
|
constructor() {
|
|
@@ -22,9 +23,6 @@ class TeamPushCommand extends api_client_command_1.default {
|
|
|
22
23
|
cli_1.default.log('Team settings already up to date');
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
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.
|
|
28
26
|
const update = { parentTeamSlugs: team.parentTeamSlugs };
|
|
29
27
|
if (team.zoneId !== undefined) {
|
|
30
28
|
update.zoneId = team.zoneId;
|
|
@@ -42,11 +40,64 @@ class TeamPushCommand extends api_client_command_1.default {
|
|
|
42
40
|
// so removing primaryLocale from it must clear the cloud setting. Empty string is the clear.
|
|
43
41
|
update.primaryLocale = team.primaryLocale ?? '';
|
|
44
42
|
await this._fcodeGraphQLClient.pushTeam(update);
|
|
43
|
+
const failedEntities = await this.syncVersioning(team, force);
|
|
44
|
+
// Re-read the cloud state so server-assigned fields (createdAt) and declined deletions
|
|
45
|
+
// land back in team.json, then hash exactly what was written.
|
|
46
|
+
const [versions, aliases] = await Promise.all([
|
|
47
|
+
this._fcodeGraphQLClient.allTeamVersions(),
|
|
48
|
+
this._fcodeGraphQLClient.allTeamVersionAliases(),
|
|
49
|
+
]);
|
|
50
|
+
this._teamRepository.applyCloudVersioning(team, versions, aliases);
|
|
51
|
+
this._teamRepository.save(team);
|
|
45
52
|
await this.workspaceConfig.setTeamStoredHash(team.contentHash);
|
|
46
53
|
cli_1.default.log(color_1.default.green('✅ team settings pushed'));
|
|
54
|
+
// Release pipelines must be able to detect an incomplete version. Re-running the
|
|
55
|
+
// command converges: it publishes only what is still missing.
|
|
56
|
+
if (failedEntities > 0) {
|
|
57
|
+
this.error(`team versions pushed with ${failedEntities} failed entities`, { exit: 1 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Makes the cloud's team versions/aliases match team.json; returns failed manifest entries. */
|
|
61
|
+
async syncVersioning(team, force) {
|
|
62
|
+
const [cloudVersions, cloudAliases] = await Promise.all([
|
|
63
|
+
this._fcodeGraphQLClient.allTeamVersions(),
|
|
64
|
+
this._fcodeGraphQLClient.allTeamVersionAliases(),
|
|
65
|
+
]);
|
|
66
|
+
const plan = (0, team_versioning_1.planTeamVersioningSync)(team, cloudVersions, cloudAliases);
|
|
67
|
+
if ((0, team_versioning_1.teamVersioningSyncPlanIsEmpty)(plan)) {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
let failedEntities = 0;
|
|
71
|
+
for (const version of plan.createVersions) {
|
|
72
|
+
cli_1.default.log(color_1.default.cyan(`🏷️ publishing team version ${version.tag}...`));
|
|
73
|
+
const teamVersion = await this._fcodeGraphQLClient.createTeamVersion(version.tag, version.comment);
|
|
74
|
+
failedEntities += (0, team_versioning_1.logTeamVersionManifest)(teamVersion);
|
|
75
|
+
}
|
|
76
|
+
for (const version of plan.deleteVersions) {
|
|
77
|
+
if (!force) {
|
|
78
|
+
const confirmed = await cli_1.default.confirm(`Team version ${version.tag} exists in the cloud but not in team.json. Deleting it also deletes ` +
|
|
79
|
+
'every process/module version with that tag, plus the aliases, executions and schedules ' +
|
|
80
|
+
'referencing them. Delete it?');
|
|
81
|
+
if (!confirmed) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
cli_1.default.log(color_1.default.cyan(`🗑️ deleting team version ${version.tag}...`));
|
|
86
|
+
await this._fcodeGraphQLClient.deleteTeamVersion(version.tag);
|
|
87
|
+
}
|
|
88
|
+
for (const alias of plan.setAliases) {
|
|
89
|
+
cli_1.default.log(color_1.default.cyan(`🔗 pointing alias ${alias.name} at ${alias.tag}...`));
|
|
90
|
+
await this._fcodeGraphQLClient.createTeamVersionAlias(alias.name, alias.tag);
|
|
91
|
+
}
|
|
92
|
+
for (const alias of plan.deleteAliases) {
|
|
93
|
+
cli_1.default.log(color_1.default.cyan(`🗑️ deleting alias ${alias.name}...`));
|
|
94
|
+
await this._fcodeGraphQLClient.deleteTeamVersionAlias(alias.id);
|
|
95
|
+
}
|
|
96
|
+
return failedEntities;
|
|
47
97
|
}
|
|
48
98
|
}
|
|
49
|
-
TeamPushCommand.description = 'Update cloud team settings (timezone, error handler, inheritance, webhook auth)
|
|
99
|
+
TeamPushCommand.description = 'Update cloud team settings (timezone, error handler, inheritance, webhook auth) and sync ' +
|
|
100
|
+
'team versions/aliases from team.json';
|
|
50
101
|
TeamPushCommand.examples = ['$ fcode team:push'];
|
|
51
102
|
TeamPushCommand.flags = types_1.ForceFlag;
|
|
52
103
|
exports.default = TeamPushCommand;
|
|
@@ -18,7 +18,13 @@ class TeamStatusCommand extends api_client_command_1.default {
|
|
|
18
18
|
}
|
|
19
19
|
const local = await this._teamRepository.find();
|
|
20
20
|
const storedHash = await this.workspaceConfig.getTeamStoredHash();
|
|
21
|
-
const
|
|
21
|
+
const [raw, versions, aliases] = await Promise.all([
|
|
22
|
+
this._fcodeGraphQLClient.team(),
|
|
23
|
+
this._fcodeGraphQLClient.allTeamVersions(),
|
|
24
|
+
this._fcodeGraphQLClient.allTeamVersionAliases(),
|
|
25
|
+
]);
|
|
26
|
+
const remote = await this._teamRepository.fromRaw(raw);
|
|
27
|
+
this._teamRepository.applyCloudVersioning(remote, versions, aliases);
|
|
22
28
|
const localChanged = storedHash !== local.contentHash;
|
|
23
29
|
const remoteChanged = storedHash !== remote.contentHash;
|
|
24
30
|
if (localChanged && remoteChanged) {
|
|
@@ -35,6 +41,6 @@ class TeamStatusCommand extends api_client_command_1.default {
|
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
|
-
TeamStatusCommand.description = 'Show whether team settings (timezone, error handler, inheritance) differ from the cloud';
|
|
44
|
+
TeamStatusCommand.description = 'Show whether team settings (timezone, error handler, inheritance) or team versions/aliases differ from the cloud';
|
|
39
45
|
TeamStatusCommand.examples = ['$ fcode team:status'];
|
|
40
46
|
exports.default = TeamStatusCommand;
|
|
@@ -4,13 +4,14 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const color_1 = require("@oclif/color");
|
|
5
5
|
const api_client_command_1 = require("../../../core/commands/api-client-command");
|
|
6
6
|
const pull_1 = require("../../pull");
|
|
7
|
+
const team_versioning_1 = require("../../../services/team-versioning");
|
|
7
8
|
const cli_1 = require("../../../utils/cli");
|
|
8
9
|
class TeamVersionsCreateCommand extends api_client_command_1.default {
|
|
9
10
|
async run() {
|
|
10
11
|
const { args: { tag }, flags: { comment }, } = await this.parse(TeamVersionsCreateCommand);
|
|
11
12
|
cli_1.default.log(color_1.default.cyan(`\n🏷️ publishing team version ${tag}...`));
|
|
12
13
|
const teamVersion = await this._fcodeGraphQLClient.createTeamVersion(tag, comment);
|
|
13
|
-
const failedCount =
|
|
14
|
+
const failedCount = (0, team_versioning_1.logTeamVersionManifest)(teamVersion);
|
|
14
15
|
// Refresh the local workspace: per-entity versions/<tag>/ folders and team.json.
|
|
15
16
|
await pull_1.default.run([]);
|
|
16
17
|
// Release pipelines must be able to detect an incomplete version. Re-running the
|
|
@@ -19,16 +20,6 @@ class TeamVersionsCreateCommand extends api_client_command_1.default {
|
|
|
19
20
|
this.error(`team version ${tag} published with ${failedCount} failed entities`, { exit: 1 });
|
|
20
21
|
}
|
|
21
22
|
}
|
|
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
23
|
}
|
|
33
24
|
TeamVersionsCreateCommand.description = 'Publish a team version: creates a version with the tag on every owned process and module ' +
|
|
34
25
|
'(existing tags are skipped) and pins bare module imports in the published snapshots';
|
|
@@ -25,7 +25,10 @@ class EnvDependenciesCommand extends api_client_command_1.default {
|
|
|
25
25
|
fs.rmSync(this.envPath, { recursive: true });
|
|
26
26
|
}
|
|
27
27
|
const manifest = await this.getManifest();
|
|
28
|
-
|
|
28
|
+
// The effective set — own dependencies over inherited ones — because that is what the platform
|
|
29
|
+
// installs, and the directory is named after the hash of what is installed.
|
|
30
|
+
const dependencies = manifest.effectiveDependencies;
|
|
31
|
+
const installationPath = this.installationPath(dependencies);
|
|
29
32
|
if (fs.existsSync(installationPath)) {
|
|
30
33
|
if (force) {
|
|
31
34
|
fs.rmSync(installationPath, { recursive: true });
|
|
@@ -38,12 +41,12 @@ class EnvDependenciesCommand extends api_client_command_1.default {
|
|
|
38
41
|
await this.createEnv();
|
|
39
42
|
cli_1.default.log(`Installing Factorial Code ${(0, types_1.programmingLanguageDisplayName)(this.programmingLanguage)} dependencies... (This process might take some time ⌛️)`);
|
|
40
43
|
cli_1.default.log('====================================');
|
|
41
|
-
await this.installDependencies(installationPath,
|
|
44
|
+
await this.installDependencies(installationPath, dependencies);
|
|
42
45
|
cli_1.default.log('====================================');
|
|
43
46
|
cli_1.default.log(color_1.default.green(`Factorial Code ${(0, types_1.programmingLanguageDisplayName)(this.programmingLanguage)} dependencies successfully installed in ${installationPath} 🤩\n\n`));
|
|
44
47
|
}
|
|
45
48
|
async check() {
|
|
46
|
-
const installationPath = this.installationPath((await this.getManifest()).
|
|
49
|
+
const installationPath = this.installationPath((await this.getManifest()).effectiveDependencies);
|
|
47
50
|
if (!fs.existsSync(installationPath)) {
|
|
48
51
|
cli_1.default.log(color_1.default.yellow(`⚠️ ${(0, types_1.programmingLanguageDisplayName)(this.programmingLanguage)} dependencies need to be installed.`));
|
|
49
52
|
}
|
|
@@ -593,13 +593,22 @@ const main = typeof _hooksModule['${fn}'] === 'function'
|
|
|
593
593
|
}
|
|
594
594
|
};
|
|
595
595
|
})(),
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
//
|
|
596
|
+
// A workspace directory holds no published snapshots, so a pinned call resolves against
|
|
597
|
+
// the files on disk — the same per-file fallback the cloud applies to a locale that was
|
|
598
|
+
// never published at that tag. A locale option resolves against that locale's own files,
|
|
599
|
+
// falling back key by key to the merged translations, exactly like the cloud executors.
|
|
599
600
|
// Own properties only and no array arguments, exactly like the cloud executors.
|
|
600
|
-
i18n: Object.assign((key, args,
|
|
601
|
+
i18n: Object.assign((key, args, options) => {
|
|
601
602
|
const translations = ${JSON.stringify(metadata.i18n.translations, null, 2)};
|
|
602
|
-
const
|
|
603
|
+
const byLocale = ${JSON.stringify(metadata.i18n.byLocale, null, 2)};
|
|
604
|
+
const requested = options && typeof options === 'object' && options.locale !== undefined && options.locale !== null
|
|
605
|
+
? String(options.locale)
|
|
606
|
+
: undefined;
|
|
607
|
+
const localeMap = requested !== undefined && Object.hasOwn(byLocale, requested) ? byLocale[requested] : undefined;
|
|
608
|
+
let template = localeMap && Object.hasOwn(localeMap, key) ? localeMap[key] : undefined;
|
|
609
|
+
if (template === undefined) {
|
|
610
|
+
template = Object.hasOwn(translations, key) ? translations[key] : undefined;
|
|
611
|
+
}
|
|
603
612
|
if (template === undefined) {
|
|
604
613
|
return key;
|
|
605
614
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -130,12 +130,30 @@ export interface ProcessForm {
|
|
|
130
130
|
appRole?: AppRole;
|
|
131
131
|
authMode?: FormAuthMode;
|
|
132
132
|
}
|
|
133
|
+
export interface ProcessUiTrigger {
|
|
134
|
+
enabled: boolean;
|
|
135
|
+
/** Identifier of the UI location the trigger is rendered in. */
|
|
136
|
+
locationId?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Label shown on the trigger. May contain fcode.i18n("key") tokens, which the platform
|
|
139
|
+
* resolves when a process listing names a locale.
|
|
140
|
+
*/
|
|
141
|
+
label?: string;
|
|
142
|
+
/** f0 icon name, e.g. "Globe". */
|
|
143
|
+
icon?: string;
|
|
144
|
+
/**
|
|
145
|
+
* When true the trigger executes the process synchronously and returns the result
|
|
146
|
+
* envelope; otherwise the execution is fire-and-forget.
|
|
147
|
+
*/
|
|
148
|
+
awaitResult?: boolean;
|
|
149
|
+
}
|
|
133
150
|
export interface ProcessMetadataFields {
|
|
134
151
|
name: string;
|
|
135
152
|
description?: string;
|
|
136
153
|
tags: string[];
|
|
137
154
|
webhook?: ProcessWebhook;
|
|
138
155
|
form?: ProcessForm;
|
|
156
|
+
uiTrigger?: ProcessUiTrigger;
|
|
139
157
|
}
|
|
140
158
|
export declare class Process extends Resource {
|
|
141
159
|
id: string;
|
|
@@ -146,6 +164,7 @@ export declare class Process extends Resource {
|
|
|
146
164
|
tags: string[];
|
|
147
165
|
webhook?: ProcessWebhook;
|
|
148
166
|
form?: ProcessForm;
|
|
167
|
+
uiTrigger?: ProcessUiTrigger;
|
|
149
168
|
script: {
|
|
150
169
|
parametersSchema: any;
|
|
151
170
|
programmingLanguage: ProgrammingLanguage;
|
|
@@ -238,7 +257,14 @@ export declare class ProgrammingLanguageManifest extends Resource {
|
|
|
238
257
|
slug: string;
|
|
239
258
|
programmingLanguage: ProgrammingLanguage;
|
|
240
259
|
dependencies: Array<Dependency>;
|
|
241
|
-
|
|
260
|
+
inheritedDependencies: Array<Dependency>;
|
|
261
|
+
constructor(id: string, slug: string, programmingLanguage: ProgrammingLanguage, dependencies: Array<Dependency>, inheritedDependencies?: Array<Dependency>);
|
|
262
|
+
/**
|
|
263
|
+
* What an execution installs: this workspace's dependencies layered over the inherited ones, one
|
|
264
|
+
* entry per package name. Mirrors the platform's merge so a local run resolves the same install
|
|
265
|
+
* directory the cloud does.
|
|
266
|
+
*/
|
|
267
|
+
get effectiveDependencies(): Array<Dependency>;
|
|
242
268
|
get contentHash(): string;
|
|
243
269
|
}
|
|
244
270
|
export interface ErrorHandlerConfig {
|
|
@@ -273,7 +299,7 @@ export interface TeamVersionAlias {
|
|
|
273
299
|
export interface TeamVersionFileEntry {
|
|
274
300
|
tag: string;
|
|
275
301
|
comment?: string;
|
|
276
|
-
createdAt
|
|
302
|
+
createdAt?: string;
|
|
277
303
|
}
|
|
278
304
|
export interface TeamAliasFileEntry {
|
|
279
305
|
name: string;
|
|
@@ -363,6 +389,7 @@ export declare const DependenciesFlags: {
|
|
|
363
389
|
export declare class Dependency {
|
|
364
390
|
name: string;
|
|
365
391
|
specifier: string;
|
|
392
|
+
resolvingTeamSlug?: string;
|
|
366
393
|
}
|
|
367
394
|
export declare class ServiceAccount extends Resource {
|
|
368
395
|
id: string;
|
package/dist/core/types.js
CHANGED
|
@@ -49,6 +49,7 @@ class Process extends Resource {
|
|
|
49
49
|
this.tags = metadata.tags;
|
|
50
50
|
this.webhook = metadata.webhook;
|
|
51
51
|
this.form = metadata.form;
|
|
52
|
+
this.uiTrigger = metadata.uiTrigger;
|
|
52
53
|
this.script = script;
|
|
53
54
|
this.readme = readme;
|
|
54
55
|
this.manifest = settings.dependencies.scopedToProcess && manifest !== null ? manifest : { dependencies: [] };
|
|
@@ -65,6 +66,9 @@ class Process extends Resource {
|
|
|
65
66
|
tags: this.tags || [],
|
|
66
67
|
webhook: this.webhook || null,
|
|
67
68
|
form: this.form || null,
|
|
69
|
+
// Inserted only when set, so every process that predates UI triggers keeps the
|
|
70
|
+
// content hash already stored in .fcode (mirrors Team.contentHash).
|
|
71
|
+
...(this.uiTrigger && { uiTrigger: this.uiTrigger }),
|
|
68
72
|
manifestDependencies: this.manifest.dependencies,
|
|
69
73
|
dependenciesScopedToProcess: this.settings.dependencies.scopedToProcess,
|
|
70
74
|
});
|
|
@@ -185,12 +189,22 @@ exports.Variable = Variable;
|
|
|
185
189
|
Variable.ENV_KEY_MAX_LENGTH = 64;
|
|
186
190
|
Variable.ENV_KEY_REGEX = /^[A-Z_a-z]\w*$/;
|
|
187
191
|
class ProgrammingLanguageManifest extends Resource {
|
|
188
|
-
constructor(id, slug, programmingLanguage, dependencies) {
|
|
192
|
+
constructor(id, slug, programmingLanguage, dependencies, inheritedDependencies = []) {
|
|
189
193
|
super();
|
|
190
194
|
this.id = id;
|
|
191
195
|
this.slug = slug;
|
|
192
196
|
this.programmingLanguage = programmingLanguage;
|
|
193
197
|
this.dependencies = dependencies;
|
|
198
|
+
this.inheritedDependencies = inheritedDependencies;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* What an execution installs: this workspace's dependencies layered over the inherited ones, one
|
|
202
|
+
* entry per package name. Mirrors the platform's merge so a local run resolves the same install
|
|
203
|
+
* directory the cloud does.
|
|
204
|
+
*/
|
|
205
|
+
get effectiveDependencies() {
|
|
206
|
+
const ownNames = new Set(this.dependencies.map((dependency) => dependency.name));
|
|
207
|
+
return [...this.dependencies, ...this.inheritedDependencies.filter((dependency) => !ownNames.has(dependency.name))];
|
|
194
208
|
}
|
|
195
209
|
get contentHash() {
|
|
196
210
|
return crypto
|
|
@@ -229,6 +243,18 @@ class Team extends Resource {
|
|
|
229
243
|
if (this.primaryLocale) {
|
|
230
244
|
payload.primaryLocale = this.primaryLocale;
|
|
231
245
|
}
|
|
246
|
+
// Versions hash only their tags and aliases their name->tag pairs: createdAt is
|
|
247
|
+
// server-assigned and a comment is immutable once published (there is no update mutation),
|
|
248
|
+
// so neither can represent a pushable local change. Inserted only when non-empty, again to
|
|
249
|
+
// keep hashes stored before versioning existed valid.
|
|
250
|
+
if (this.versions && this.versions.length > 0) {
|
|
251
|
+
payload.versions = this.versions.map(({ tag }) => tag).sort();
|
|
252
|
+
}
|
|
253
|
+
if (this.aliases && this.aliases.length > 0) {
|
|
254
|
+
payload.aliases = this.aliases
|
|
255
|
+
.map(({ name, tag }) => ({ name, tag }))
|
|
256
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
257
|
+
}
|
|
232
258
|
return (0, canonical_hash_1.canonicalJsonHash)(payload);
|
|
233
259
|
}
|
|
234
260
|
}
|
|
@@ -12,7 +12,10 @@ async function getDependenciesPath(programmingLanguage) {
|
|
|
12
12
|
const basePath = (0, workspace_1.baseDependenciesPath)(programmingLanguage);
|
|
13
13
|
const repo = new programming_language_manifest_1.default();
|
|
14
14
|
const programmingLanguageManifest = await repo.find(programmingLanguage);
|
|
15
|
-
|
|
15
|
+
// The effective set, not the workspace's own: the cloud installs the inherited dependencies too,
|
|
16
|
+
// and the directory is named after the hash of what is installed. Hashing only the own list here
|
|
17
|
+
// would make a local run resolve a different directory than the platform.
|
|
18
|
+
const dependenciesDirectory = (0, env_dependencies_command_1.dependenciesHash)(programmingLanguageManifest.effectiveDependencies);
|
|
16
19
|
return path.join(basePath, dependenciesDirectory);
|
|
17
20
|
}
|
|
18
21
|
function getProcessDependenciesPath(process) {
|
package/dist/env.js
CHANGED
|
@@ -14,7 +14,7 @@ const zod_1 = require("zod");
|
|
|
14
14
|
*/
|
|
15
15
|
exports.envSchema = zod_1.z.object({
|
|
16
16
|
// --- Cloud / auth endpoints ---
|
|
17
|
-
FCODE_CLOUD_URL: zod_1.z.string().min(1).default('https://code.
|
|
17
|
+
FCODE_CLOUD_URL: zod_1.z.string().min(1).default('https://code.factorialhr.com/platform'),
|
|
18
18
|
FCODE_AUTH_URL: zod_1.z.string().min(1).optional(),
|
|
19
19
|
FCODE_CLIENT_ID: zod_1.z.string().min(1).default('cli'),
|
|
20
20
|
// --- Local config location (defaults to os.homedir() at the call site) ---
|
|
@@ -136,6 +136,7 @@ class ProcessRepository extends resource_1.default {
|
|
|
136
136
|
tags: Array.isArray(raw.tags) ? raw.tags : [],
|
|
137
137
|
webhook: raw.webhook,
|
|
138
138
|
form: raw.form,
|
|
139
|
+
uiTrigger: raw.uiTrigger,
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
catch (error) {
|
|
@@ -156,6 +157,9 @@ class ProcessRepository extends resource_1.default {
|
|
|
156
157
|
if (process.form !== undefined) {
|
|
157
158
|
metadata.form = process.form;
|
|
158
159
|
}
|
|
160
|
+
if (process.uiTrigger !== undefined) {
|
|
161
|
+
metadata.uiTrigger = process.uiTrigger;
|
|
162
|
+
}
|
|
159
163
|
return metadata;
|
|
160
164
|
}
|
|
161
165
|
async findParameters(processSlug) {
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProgrammingLanguage, ProgrammingLanguageManifest, ResourceMetadata } from '../core/types';
|
|
2
2
|
import ResourceRepositoryBase from './resource';
|
|
3
|
+
/**
|
|
4
|
+
* The inherited file each language collapses its parents into. Exported so the gitignore sync names
|
|
5
|
+
* exactly what this repository writes, rather than repeating the strings.
|
|
6
|
+
*/
|
|
7
|
+
export declare const INHERITED_FILE_NAME: Record<ProgrammingLanguage, string>;
|
|
8
|
+
export declare const DEPENDENCIES_DIR = "dependencies";
|
|
3
9
|
declare class ProgrammingLanguageManifestRepository extends ResourceRepositoryBase<ProgrammingLanguageManifest> {
|
|
4
10
|
constructor();
|
|
5
11
|
find(slug: string): Promise<ProgrammingLanguageManifest>;
|
|
6
12
|
protected getSourceCodeFileName(programmingLanguageManifest: ProgrammingLanguageManifest): string;
|
|
13
|
+
/**
|
|
14
|
+
* The inherited dependencies of one language, collapsed into a single file the way inherited
|
|
15
|
+
* variables are (`variables.inherited.env`) rather than one file per parent: two versions of the
|
|
16
|
+
* same package cannot be installed side by side, so there is only ever one winner per name and
|
|
17
|
+
* the platform has already picked it.
|
|
18
|
+
*/
|
|
19
|
+
protected getInheritedFileName(programmingLanguage: ProgrammingLanguage): string;
|
|
20
|
+
getInheritedPath(programmingLanguage: ProgrammingLanguage): string;
|
|
21
|
+
private readInheritedDependencies;
|
|
7
22
|
protected getResourceDirectory(programmingLanguageManifest: ProgrammingLanguageManifest): string;
|
|
8
23
|
protected getResourcePath(programmingLanguageManifest: ProgrammingLanguageManifest): string;
|
|
9
24
|
protected removeFiles(programmingLanguageManifest: ProgrammingLanguageManifest): Promise<void>;
|
|
10
25
|
protected resourceToMetadata(programmingLanguageManifest: ProgrammingLanguageManifest): ResourceMetadata;
|
|
11
26
|
protected saveFiles(programmingLanguageManifest: ProgrammingLanguageManifest): Promise<void>;
|
|
27
|
+
private removeInheritedFile;
|
|
12
28
|
metadataFromFiles(slug: string): Promise<ResourceMetadata>;
|
|
13
29
|
validSlug(slug: string): boolean;
|
|
14
30
|
slugFormatMessage(): string;
|