@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEPENDENCIES_DIR = exports.INHERITED_FILE_NAME = void 0;
|
|
3
4
|
const fs = require("fs-extra");
|
|
4
5
|
const path = require("node:path");
|
|
5
6
|
const errors_1 = require("../core/errors");
|
|
@@ -7,6 +8,15 @@ const types_1 = require("../core/types");
|
|
|
7
8
|
const resource_1 = require("./resource");
|
|
8
9
|
const dependencies_1 = require("../core/utils/dependencies");
|
|
9
10
|
const file_utils_1 = require("../utils/file-utils");
|
|
11
|
+
/**
|
|
12
|
+
* The inherited file each language collapses its parents into. Exported so the gitignore sync names
|
|
13
|
+
* exactly what this repository writes, rather than repeating the strings.
|
|
14
|
+
*/
|
|
15
|
+
exports.INHERITED_FILE_NAME = {
|
|
16
|
+
JAVASCRIPT: 'package.inherited.json',
|
|
17
|
+
PYTHON: 'requirements.inherited.txt',
|
|
18
|
+
};
|
|
19
|
+
exports.DEPENDENCIES_DIR = 'dependencies';
|
|
10
20
|
class ProgrammingLanguageManifestRepository extends resource_1.default {
|
|
11
21
|
constructor() {
|
|
12
22
|
super({
|
|
@@ -21,7 +31,7 @@ class ProgrammingLanguageManifestRepository extends resource_1.default {
|
|
|
21
31
|
}
|
|
22
32
|
const sourceCodeFileContent = fs.readFileSync(this.getResourcePath(programmingLanguageManifest));
|
|
23
33
|
try {
|
|
24
|
-
return new types_1.ProgrammingLanguageManifest(programmingLanguageManifest.id, programmingLanguageManifest.slug, programmingLanguageManifest.programmingLanguage, (0, dependencies_1.dependenciesFromFile)(programmingLanguageManifest.programmingLanguage, sourceCodeFileContent.toString()));
|
|
34
|
+
return new types_1.ProgrammingLanguageManifest(programmingLanguageManifest.id, programmingLanguageManifest.slug, programmingLanguageManifest.programmingLanguage, (0, dependencies_1.dependenciesFromFile)(programmingLanguageManifest.programmingLanguage, sourceCodeFileContent.toString()), this.readInheritedDependencies(programmingLanguageManifest.programmingLanguage));
|
|
25
35
|
}
|
|
26
36
|
catch (error) {
|
|
27
37
|
throw new errors_1.LocalProgrammingLanguageManifestNotFound(error, slug);
|
|
@@ -30,6 +40,25 @@ class ProgrammingLanguageManifestRepository extends resource_1.default {
|
|
|
30
40
|
getSourceCodeFileName(programmingLanguageManifest) {
|
|
31
41
|
return programmingLanguageManifest.programmingLanguage === 'JAVASCRIPT' ? 'package.json' : 'requirements.txt';
|
|
32
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The inherited dependencies of one language, collapsed into a single file the way inherited
|
|
45
|
+
* variables are (`variables.inherited.env`) rather than one file per parent: two versions of the
|
|
46
|
+
* same package cannot be installed side by side, so there is only ever one winner per name and
|
|
47
|
+
* the platform has already picked it.
|
|
48
|
+
*/
|
|
49
|
+
getInheritedFileName(programmingLanguage) {
|
|
50
|
+
return exports.INHERITED_FILE_NAME[programmingLanguage];
|
|
51
|
+
}
|
|
52
|
+
getInheritedPath(programmingLanguage) {
|
|
53
|
+
return path.join(this._repositoryName, this.getInheritedFileName(programmingLanguage));
|
|
54
|
+
}
|
|
55
|
+
readInheritedDependencies(programmingLanguage) {
|
|
56
|
+
const inheritedPath = this.getInheritedPath(programmingLanguage);
|
|
57
|
+
if (!fs.existsSync(inheritedPath)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return (0, dependencies_1.dependenciesFromFile)(programmingLanguage, fs.readFileSync(inheritedPath).toString());
|
|
61
|
+
}
|
|
33
62
|
getResourceDirectory(programmingLanguageManifest) {
|
|
34
63
|
return this._repositoryName;
|
|
35
64
|
}
|
|
@@ -40,6 +69,7 @@ class ProgrammingLanguageManifestRepository extends resource_1.default {
|
|
|
40
69
|
await new Promise((resolve, reject) => {
|
|
41
70
|
try {
|
|
42
71
|
fs.removeSync(this.getResourcePath(programmingLanguageManifest));
|
|
72
|
+
this.removeInheritedFile(programmingLanguageManifest.programmingLanguage);
|
|
43
73
|
this.removeDirectoryIfEmpty(this.getResourceDirectory(programmingLanguageManifest));
|
|
44
74
|
resolve(true);
|
|
45
75
|
}
|
|
@@ -57,6 +87,26 @@ class ProgrammingLanguageManifestRepository extends resource_1.default {
|
|
|
57
87
|
}
|
|
58
88
|
async saveFiles(programmingLanguageManifest) {
|
|
59
89
|
(0, file_utils_1.outputFileSyncIfChanged)(this.getResourcePath(programmingLanguageManifest), (0, dependencies_1.dependenciesToFile)(programmingLanguageManifest.programmingLanguage, programmingLanguageManifest.dependencies));
|
|
90
|
+
const { programmingLanguage, inheritedDependencies } = programmingLanguageManifest;
|
|
91
|
+
if (inheritedDependencies.length === 0) {
|
|
92
|
+
// The workspace stopped inheriting anything (a parent was removed, or emptied its manifest):
|
|
93
|
+
// a stale file would keep being installed locally and keep showing in `status`.
|
|
94
|
+
this.removeInheritedFile(programmingLanguage);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Read-only, like inherited locales and modules: these belong to a parent workspace, and
|
|
98
|
+
// editing one here would change nothing — declare the name in package.json to override it.
|
|
99
|
+
(0, file_utils_1.outputFileSyncIfChanged)(this.getInheritedPath(programmingLanguage), (0, dependencies_1.dependenciesToFile)(programmingLanguage, inheritedDependencies), {
|
|
100
|
+
readOnly: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
removeInheritedFile(programmingLanguage) {
|
|
104
|
+
const inheritedPath = this.getInheritedPath(programmingLanguage);
|
|
105
|
+
if (fs.existsSync(inheritedPath)) {
|
|
106
|
+
// Written read-only, so it has to be made writable before it can go.
|
|
107
|
+
fs.chmodSync(inheritedPath, 0o644);
|
|
108
|
+
fs.removeSync(inheritedPath);
|
|
109
|
+
}
|
|
60
110
|
}
|
|
61
111
|
async metadataFromFiles(slug) {
|
|
62
112
|
if (slug === 'JAVASCRIPT') {
|
|
@@ -17,14 +17,24 @@ export default class TeamRepository {
|
|
|
17
17
|
/** Builds a Team from the cloud's raw settings, resolving the error-handler id to a slug. */
|
|
18
18
|
fromRaw(raw: RawTeamSettings): Promise<Team>;
|
|
19
19
|
/**
|
|
20
|
-
* Writes the cloud's team versions and aliases into the Team as the
|
|
21
|
-
* fields (sorted for stable git diffs).
|
|
22
|
-
*
|
|
20
|
+
* Writes the cloud's team versions and aliases into the Team as the team.json versioning
|
|
21
|
+
* fields (sorted for stable git diffs). Cloud entries always carry a createdAt; only
|
|
22
|
+
* hand-added local entries lack one, and those never pass through here.
|
|
23
23
|
*/
|
|
24
24
|
applyCloudVersioning(team: Team, versions: TeamVersion[], aliases: TeamVersionAlias[]): void;
|
|
25
25
|
resolveSlugToId(processSlug: string): Promise<string>;
|
|
26
26
|
resolveIdToSlug(processId: string): Promise<string>;
|
|
27
27
|
private parseErrorHandler;
|
|
28
|
+
/**
|
|
29
|
+
* team.json is hand-edited to declare versions, so entries without a tag are dropped and a
|
|
30
|
+
* duplicated tag keeps its first entry (a tag names one immutable snapshot).
|
|
31
|
+
*/
|
|
32
|
+
private parseVersions;
|
|
33
|
+
/**
|
|
34
|
+
* An alias is a name->tag pointer; entries missing either half are dropped and a duplicated
|
|
35
|
+
* name keeps its first entry.
|
|
36
|
+
*/
|
|
37
|
+
private parseAliases;
|
|
28
38
|
/** A configuration without a variable key is meaningless, so it is read as absent. */
|
|
29
39
|
private parseWebhookAuth;
|
|
30
40
|
private toFile;
|
|
@@ -30,12 +30,11 @@ class TeamRepository {
|
|
|
30
30
|
catch { }
|
|
31
31
|
const parentTeamSlugs = Array.isArray(raw.parentTeamSlugs) ? raw.parentTeamSlugs : [];
|
|
32
32
|
const team = new types_1.Team(slug, parentTeamSlugs, raw.zoneId, this.parseErrorHandler(raw.errorHandlerConfig), this.parseWebhookAuth(raw.webhookAuth), typeof raw.primaryLocale === 'string' && raw.primaryLocale.length > 0 ? raw.primaryLocale : undefined);
|
|
33
|
-
// Parsed so a find()+save() round-trip does not drop the pull-only fields.
|
|
34
33
|
if (Array.isArray(raw.versions)) {
|
|
35
|
-
team.versions = raw.versions;
|
|
34
|
+
team.versions = this.parseVersions(raw.versions);
|
|
36
35
|
}
|
|
37
36
|
if (Array.isArray(raw.aliases)) {
|
|
38
|
-
team.aliases = raw.aliases;
|
|
37
|
+
team.aliases = this.parseAliases(raw.aliases);
|
|
39
38
|
}
|
|
40
39
|
return team;
|
|
41
40
|
}
|
|
@@ -54,9 +53,9 @@ class TeamRepository {
|
|
|
54
53
|
return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig, this.parseWebhookAuth(raw.webhookAuth), raw.primaryLocale ?? undefined);
|
|
55
54
|
}
|
|
56
55
|
/**
|
|
57
|
-
* Writes the cloud's team versions and aliases into the Team as the
|
|
58
|
-
* fields (sorted for stable git diffs).
|
|
59
|
-
*
|
|
56
|
+
* Writes the cloud's team versions and aliases into the Team as the team.json versioning
|
|
57
|
+
* fields (sorted for stable git diffs). Cloud entries always carry a createdAt; only
|
|
58
|
+
* hand-added local entries lack one, and those never pass through here.
|
|
60
59
|
*/
|
|
61
60
|
applyCloudVersioning(team, versions, aliases) {
|
|
62
61
|
team.versions = versions
|
|
@@ -67,7 +66,7 @@ class TeamRepository {
|
|
|
67
66
|
}
|
|
68
67
|
return entry;
|
|
69
68
|
})
|
|
70
|
-
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
69
|
+
.sort((a, b) => (a.createdAt ?? '').localeCompare(b.createdAt ?? ''));
|
|
71
70
|
team.aliases = aliases
|
|
72
71
|
.map((alias) => ({ name: alias.name, tag: alias.tag }))
|
|
73
72
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -87,6 +86,50 @@ class TeamRepository {
|
|
|
87
86
|
}
|
|
88
87
|
return { processSlug: raw.processSlug, tag: raw.tag ?? null };
|
|
89
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* team.json is hand-edited to declare versions, so entries without a tag are dropped and a
|
|
91
|
+
* duplicated tag keeps its first entry (a tag names one immutable snapshot).
|
|
92
|
+
*/
|
|
93
|
+
parseVersions(raw) {
|
|
94
|
+
const versions = [];
|
|
95
|
+
for (const candidate of raw) {
|
|
96
|
+
if (!candidate || typeof candidate.tag !== 'string' || candidate.tag.length === 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (versions.some(({ tag }) => tag === candidate.tag)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const entry = { tag: candidate.tag };
|
|
103
|
+
if (typeof candidate.comment === 'string' && candidate.comment.length > 0) {
|
|
104
|
+
entry.comment = candidate.comment;
|
|
105
|
+
}
|
|
106
|
+
if (typeof candidate.createdAt === 'string' && candidate.createdAt.length > 0) {
|
|
107
|
+
entry.createdAt = candidate.createdAt;
|
|
108
|
+
}
|
|
109
|
+
versions.push(entry);
|
|
110
|
+
}
|
|
111
|
+
return versions;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* An alias is a name->tag pointer; entries missing either half are dropped and a duplicated
|
|
115
|
+
* name keeps its first entry.
|
|
116
|
+
*/
|
|
117
|
+
parseAliases(raw) {
|
|
118
|
+
const aliases = [];
|
|
119
|
+
for (const candidate of raw) {
|
|
120
|
+
if (!candidate || typeof candidate.name !== 'string' || candidate.name.length === 0) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (typeof candidate.tag !== 'string' || candidate.tag.length === 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (aliases.some(({ name }) => name === candidate.name)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
aliases.push({ name: candidate.name, tag: candidate.tag });
|
|
130
|
+
}
|
|
131
|
+
return aliases;
|
|
132
|
+
}
|
|
90
133
|
/** A configuration without a variable key is meaningless, so it is read as absent. */
|
|
91
134
|
parseWebhookAuth(raw) {
|
|
92
135
|
if (!raw || typeof raw.variableKey !== 'string' || raw.variableKey.length === 0) {
|
|
@@ -114,7 +157,7 @@ class TeamRepository {
|
|
|
114
157
|
if (team.primaryLocale !== undefined) {
|
|
115
158
|
file.primaryLocale = team.primaryLocale;
|
|
116
159
|
}
|
|
117
|
-
//
|
|
160
|
+
// Global versioning fields, appended after the scalar settings.
|
|
118
161
|
if (team.versions !== undefined) {
|
|
119
162
|
file.versions = team.versions;
|
|
120
163
|
}
|
|
@@ -81,22 +81,29 @@ class Processes(dict):
|
|
|
81
81
|
class I18n:
|
|
82
82
|
"""Local equivalent of the cloud's fcode.i18n.
|
|
83
83
|
|
|
84
|
-
The CLI has already layered the chosen locale over the workspace's primary one, so
|
|
85
|
-
arrives here is
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
The CLI has already layered the chosen locale over the workspace's primary one, so the base
|
|
85
|
+
map that arrives here is flat. ``byLocale`` carries each locale's own files, so a call naming
|
|
86
|
+
a locale (``{"locale": "es"}``) resolves against that locale first and falls back key by key
|
|
87
|
+
to the base map, exactly like the cloud executors. Nothing raises: a key with no translation
|
|
88
|
+
resolves to the key itself, and a placeholder with no argument is left exactly as written.
|
|
89
|
+
|
|
90
|
+
The version a call pins in the cloud is accepted and ignored: a workspace directory holds no
|
|
91
|
+
published snapshots, so a pinned call resolves against the files on disk -- the same per-file
|
|
92
|
+
fallback the cloud applies to a locale never published at that tag.
|
|
91
93
|
"""
|
|
92
94
|
|
|
93
95
|
def __init__(self, i18n_payload):
|
|
94
96
|
payload = i18n_payload or {}
|
|
95
97
|
self.__translations = payload.get("translations") or {}
|
|
98
|
+
self.__by_locale = payload.get("byLocale") or {}
|
|
96
99
|
self.locale = payload.get("locale")
|
|
97
100
|
|
|
98
101
|
def __call__(self, key, args=None, options=None):
|
|
99
|
-
|
|
102
|
+
locale = options.get("locale") if isinstance(options, dict) else None
|
|
103
|
+
locale_map = self.__by_locale.get(str(locale)) if locale is not None else None
|
|
104
|
+
template = locale_map.get(key) if locale_map is not None else None
|
|
105
|
+
if template is None:
|
|
106
|
+
template = self.__translations.get(key)
|
|
100
107
|
if template is None:
|
|
101
108
|
return key
|
|
102
109
|
if not isinstance(args, dict):
|
|
@@ -68,6 +68,7 @@ declare class FcodeGraphQLClient {
|
|
|
68
68
|
team(): Promise<RawTeamSettings>;
|
|
69
69
|
pushTeam(update: TeamUpdateInput): Promise<RawTeamSettings>;
|
|
70
70
|
mapProgrammingLanguageManifest(programmingLanguageManifest: any): ProgrammingLanguageManifest;
|
|
71
|
+
private mapDependencies;
|
|
71
72
|
allProgrammingLanguageManifest(): Promise<Array<ProgrammingLanguageManifest>>;
|
|
72
73
|
pushProgrammingLanguageManifest(programmingLanguageManifest: ProgrammingLanguageManifest): Promise<ProgrammingLanguageManifest>;
|
|
73
74
|
allServiceAccounts(): Promise<Array<ServiceAccount>>;
|
|
@@ -4,6 +4,10 @@ const graphql_request_1 = require("graphql-request");
|
|
|
4
4
|
const types_1 = require("../core/types");
|
|
5
5
|
const locale_merger_1 = require("./locale-merger");
|
|
6
6
|
const _ = require("lodash");
|
|
7
|
+
// Manifest fields an fcode-api older than this CLI may not know. The manifest query falls back to a
|
|
8
|
+
// narrower selection when the error names one of them, so a CLI stays usable against an
|
|
9
|
+
// on-premises api that has not been updated yet.
|
|
10
|
+
const UNKNOWN_MANIFEST_FIELDS = ['isNative', 'inheritedDependencies'];
|
|
7
11
|
// Shared team version selections so every query/mutation returns the same shape.
|
|
8
12
|
const TEAM_VERSION_FIELDS = `
|
|
9
13
|
id
|
|
@@ -67,6 +71,13 @@ const PROCESS_FIELDS = `
|
|
|
67
71
|
appRole
|
|
68
72
|
authMode
|
|
69
73
|
}
|
|
74
|
+
uiTrigger {
|
|
75
|
+
enabled
|
|
76
|
+
locationId
|
|
77
|
+
label
|
|
78
|
+
icon
|
|
79
|
+
awaitResult
|
|
80
|
+
}
|
|
70
81
|
dependencies {
|
|
71
82
|
scopedToProcess
|
|
72
83
|
}
|
|
@@ -429,6 +440,21 @@ class FcodeGraphQLClient {
|
|
|
429
440
|
settings.formSettings.authMode = process.form.authMode;
|
|
430
441
|
}
|
|
431
442
|
}
|
|
443
|
+
if (process.uiTrigger) {
|
|
444
|
+
settings.uiTrigger = { enabled: process.uiTrigger.enabled };
|
|
445
|
+
if (process.uiTrigger.locationId) {
|
|
446
|
+
settings.uiTrigger.locationId = process.uiTrigger.locationId;
|
|
447
|
+
}
|
|
448
|
+
if (process.uiTrigger.label) {
|
|
449
|
+
settings.uiTrigger.label = process.uiTrigger.label;
|
|
450
|
+
}
|
|
451
|
+
if (process.uiTrigger.icon) {
|
|
452
|
+
settings.uiTrigger.icon = process.uiTrigger.icon;
|
|
453
|
+
}
|
|
454
|
+
if (process.uiTrigger.awaitResult !== undefined) {
|
|
455
|
+
settings.uiTrigger.awaitResult = process.uiTrigger.awaitResult;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
432
458
|
const input = {
|
|
433
459
|
name: process.name,
|
|
434
460
|
parametersSchema: process.script.parametersSchema,
|
|
@@ -645,6 +671,13 @@ class FcodeGraphQLClient {
|
|
|
645
671
|
isNative
|
|
646
672
|
gitUrl
|
|
647
673
|
}
|
|
674
|
+
inheritedDependencies {
|
|
675
|
+
name
|
|
676
|
+
semVer
|
|
677
|
+
isNative
|
|
678
|
+
gitUrl
|
|
679
|
+
resolvingTeamSlug
|
|
680
|
+
}
|
|
648
681
|
}
|
|
649
682
|
}
|
|
650
683
|
`, { programmingLanguage })
|
|
@@ -654,7 +687,10 @@ class FcodeGraphQLClient {
|
|
|
654
687
|
.catch((error) => {
|
|
655
688
|
// TODO: This is a workaround backward compatibility with old versions of fcode-api.
|
|
656
689
|
// Remove when full on-premises installations are updated.
|
|
657
|
-
|
|
690
|
+
// `inheritedDependencies` is listed for the same reason `isNative` is: an api predating
|
|
691
|
+
// dependency inheritance rejects the whole query over the unknown field, and the fallback
|
|
692
|
+
// below simply reads no inherited dependencies.
|
|
693
|
+
if (error.response?.errors?.[0]?.message && UNKNOWN_MANIFEST_FIELDS.some((field) => error.response.errors[0].message.includes(field))) {
|
|
658
694
|
return this.client
|
|
659
695
|
.request((0, graphql_request_1.gql) `
|
|
660
696
|
query getProgrammingLanguageManifest($programmingLanguage: ProgrammingLanguage!) {
|
|
@@ -732,6 +768,21 @@ class FcodeGraphQLClient {
|
|
|
732
768
|
metadata.form.authMode = settings.formSettings.authMode;
|
|
733
769
|
}
|
|
734
770
|
}
|
|
771
|
+
if (settings?.uiTrigger) {
|
|
772
|
+
metadata.uiTrigger = { enabled: Boolean(settings.uiTrigger.enabled) };
|
|
773
|
+
if (settings.uiTrigger.locationId) {
|
|
774
|
+
metadata.uiTrigger.locationId = settings.uiTrigger.locationId;
|
|
775
|
+
}
|
|
776
|
+
if (settings.uiTrigger.label) {
|
|
777
|
+
metadata.uiTrigger.label = settings.uiTrigger.label;
|
|
778
|
+
}
|
|
779
|
+
if (settings.uiTrigger.icon) {
|
|
780
|
+
metadata.uiTrigger.icon = settings.uiTrigger.icon;
|
|
781
|
+
}
|
|
782
|
+
if (settings.uiTrigger.awaitResult) {
|
|
783
|
+
metadata.uiTrigger.awaitResult = true;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
735
786
|
return new types_1.Process(id, slug, script, readme, { dependencies }, settings, metadata, this.inheritedTeam(resolvingTeamSlug));
|
|
736
787
|
}
|
|
737
788
|
/**
|
|
@@ -941,15 +992,23 @@ class FcodeGraphQLClient {
|
|
|
941
992
|
}
|
|
942
993
|
mapProgrammingLanguageManifest(programmingLanguageManifest) {
|
|
943
994
|
const allDependencies = programmingLanguageManifest.nextInstallation?.dependencies || programmingLanguageManifest.dependencies;
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
995
|
+
return new types_1.ProgrammingLanguageManifest(programmingLanguageManifest.id, programmingLanguageManifest.programmingLanguage, programmingLanguageManifest.programmingLanguage, this.mapDependencies(allDependencies),
|
|
996
|
+
// Absent when the api predates dependency inheritance, or when the workspace has no parents.
|
|
997
|
+
this.mapDependencies(programmingLanguageManifest.inheritedDependencies));
|
|
998
|
+
}
|
|
999
|
+
mapDependencies(dependencies) {
|
|
1000
|
+
return _.map(dependencies, (dependency) => {
|
|
1001
|
+
if (dependency.isNative) {
|
|
1002
|
+
return null;
|
|
947
1003
|
}
|
|
948
|
-
return
|
|
1004
|
+
return {
|
|
1005
|
+
name: dependency.name,
|
|
1006
|
+
specifier: dependency.gitUrl ?? dependency.semVer,
|
|
1007
|
+
...(dependency.resolvingTeamSlug && { resolvingTeamSlug: dependency.resolvingTeamSlug }),
|
|
1008
|
+
};
|
|
949
1009
|
})
|
|
950
1010
|
.filter(Boolean)
|
|
951
|
-
.sort((a, b) => a
|
|
952
|
-
return new types_1.ProgrammingLanguageManifest(programmingLanguageManifest.id, programmingLanguageManifest.programmingLanguage, programmingLanguageManifest.programmingLanguage, dependencies);
|
|
1011
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
953
1012
|
}
|
|
954
1013
|
allProgrammingLanguageManifest() {
|
|
955
1014
|
const result = [];
|
|
@@ -983,7 +1042,11 @@ class FcodeGraphQLClient {
|
|
|
983
1042
|
}
|
|
984
1043
|
`, {
|
|
985
1044
|
id: programmingLanguageManifest.id,
|
|
986
|
-
input: {
|
|
1045
|
+
input: {
|
|
1046
|
+
// Only name and specifier: DependencyUpdateInput has no other fields, and a workspace
|
|
1047
|
+
// pushes its own list — inherited entries live in a separate file and never get here.
|
|
1048
|
+
dependencies: programmingLanguageManifest.dependencies.map(({ name, specifier }) => ({ name, specifier })),
|
|
1049
|
+
},
|
|
987
1050
|
})
|
|
988
1051
|
.then(({ updateProgrammingLanguageManifest: programmingLanguageManifest }) => {
|
|
989
1052
|
return this.client
|
|
@@ -10,9 +10,14 @@
|
|
|
10
10
|
export declare function flattenTranslations(document: unknown, prefix?: string, target?: Record<string, string>): Record<string, string>;
|
|
11
11
|
/**
|
|
12
12
|
* @param requestedLocale the locale to run with, or undefined to use the workspace's primary
|
|
13
|
-
* @returns the locale actually used and its flat translations, with the primary locale underneath
|
|
13
|
+
* @returns the locale actually used and its flat translations, with the primary locale underneath,
|
|
14
|
+
* plus each locale's own map (inherited file layered under its own, but NOT merged over the
|
|
15
|
+
* primary) so a call naming a locale (`fcode.i18n(key, args, { locale })`) resolves like the
|
|
16
|
+
* cloud's `by_locale`: key by key in the requested locale first, falling back to the merged
|
|
17
|
+
* translations
|
|
14
18
|
*/
|
|
15
19
|
export declare function resolveLocalTranslations(requestedLocale?: string, workspaceDir?: string): {
|
|
16
20
|
locale?: string;
|
|
17
21
|
translations: Record<string, string>;
|
|
22
|
+
byLocale: Record<string, Record<string, string>>;
|
|
18
23
|
};
|
|
@@ -107,14 +107,21 @@ function primaryLocaleOf(workspaceDir) {
|
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
109
109
|
* @param requestedLocale the locale to run with, or undefined to use the workspace's primary
|
|
110
|
-
* @returns the locale actually used and its flat translations, with the primary locale underneath
|
|
110
|
+
* @returns the locale actually used and its flat translations, with the primary locale underneath,
|
|
111
|
+
* plus each locale's own map (inherited file layered under its own, but NOT merged over the
|
|
112
|
+
* primary) so a call naming a locale (`fcode.i18n(key, args, { locale })`) resolves like the
|
|
113
|
+
* cloud's `by_locale`: key by key in the requested locale first, falling back to the merged
|
|
114
|
+
* translations
|
|
111
115
|
*/
|
|
112
116
|
function resolveLocalTranslations(requestedLocale, workspaceDir = process.cwd()) {
|
|
113
117
|
const primary = primaryLocaleOf(workspaceDir);
|
|
114
118
|
const locale = requestedLocale || primary;
|
|
119
|
+
const byLocale = Object.fromEntries(availableLocales(workspaceDir).map((slug) => [slug, readLocale(workspaceDir, slug)]));
|
|
115
120
|
if (!locale) {
|
|
116
|
-
return { locale: undefined, translations: {} };
|
|
121
|
+
return { locale: undefined, translations: {}, byLocale };
|
|
117
122
|
}
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
// `byLocale` already holds every locale's own file, so the merged map — the requested locale
|
|
124
|
+
// layered over the primary — is derived from it rather than read from disk a second time.
|
|
125
|
+
const fallback = primary ? byLocale[primary] : {};
|
|
126
|
+
return { locale, translations: { ...fallback, ...(byLocale[locale] ?? {}) }, byLocale };
|
|
120
127
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Team, TeamAliasFileEntry, TeamVersion, TeamVersionAlias, TeamVersionFileEntry } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* What a team:push must do to make the cloud's team versions and aliases match team.json.
|
|
4
|
+
* Versions are only ever created or deleted (tags are immutable snapshots); aliases are
|
|
5
|
+
* name->tag pointers, so a changed tag is an upsert of the same name.
|
|
6
|
+
*/
|
|
7
|
+
export interface TeamVersioningSyncPlan {
|
|
8
|
+
createVersions: TeamVersionFileEntry[];
|
|
9
|
+
deleteVersions: TeamVersion[];
|
|
10
|
+
setAliases: TeamAliasFileEntry[];
|
|
11
|
+
deleteAliases: TeamVersionAlias[];
|
|
12
|
+
}
|
|
13
|
+
export declare function planTeamVersioningSync(team: Team, cloudVersions: TeamVersion[], cloudAliases: TeamVersionAlias[]): TeamVersioningSyncPlan;
|
|
14
|
+
export declare function teamVersioningSyncPlanIsEmpty(plan: TeamVersioningSyncPlan): boolean;
|
|
15
|
+
/** Logs the per-entity outcome of publishing a team version and returns how many failed. */
|
|
16
|
+
export declare function logTeamVersionManifest(teamVersion: TeamVersion): number;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planTeamVersioningSync = planTeamVersioningSync;
|
|
4
|
+
exports.teamVersioningSyncPlanIsEmpty = teamVersioningSyncPlanIsEmpty;
|
|
5
|
+
exports.logTeamVersionManifest = logTeamVersionManifest;
|
|
6
|
+
const color_1 = require("@oclif/color");
|
|
7
|
+
const cli_1 = require("../utils/cli");
|
|
8
|
+
function planTeamVersioningSync(team, cloudVersions, cloudAliases) {
|
|
9
|
+
const localVersions = team.versions ?? [];
|
|
10
|
+
const localAliases = team.aliases ?? [];
|
|
11
|
+
const cloudVersionTags = new Set(cloudVersions.map(({ tag }) => tag));
|
|
12
|
+
const localVersionTags = new Set(localVersions.map(({ tag }) => tag));
|
|
13
|
+
const cloudAliasesByName = new Map(cloudAliases.map((alias) => [alias.name, alias]));
|
|
14
|
+
const localAliasNames = new Set(localAliases.map(({ name }) => name));
|
|
15
|
+
return {
|
|
16
|
+
createVersions: localVersions.filter(({ tag }) => !cloudVersionTags.has(tag)),
|
|
17
|
+
deleteVersions: cloudVersions.filter(({ tag }) => !localVersionTags.has(tag)),
|
|
18
|
+
setAliases: localAliases.filter(({ name, tag }) => cloudAliasesByName.get(name)?.tag !== tag),
|
|
19
|
+
deleteAliases: cloudAliases.filter(({ name }) => !localAliasNames.has(name)),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function teamVersioningSyncPlanIsEmpty(plan) {
|
|
23
|
+
return (plan.createVersions.length === 0 &&
|
|
24
|
+
plan.deleteVersions.length === 0 &&
|
|
25
|
+
plan.setAliases.length === 0 &&
|
|
26
|
+
plan.deleteAliases.length === 0);
|
|
27
|
+
}
|
|
28
|
+
/** Logs the per-entity outcome of publishing a team version and returns how many failed. */
|
|
29
|
+
function logTeamVersionManifest(teamVersion) {
|
|
30
|
+
const created = teamVersion.manifest.filter(({ outcome }) => outcome === 'CREATED');
|
|
31
|
+
const skipped = teamVersion.manifest.filter(({ outcome }) => outcome === 'SKIPPED_EXISTS');
|
|
32
|
+
const failed = teamVersion.manifest.filter(({ outcome }) => outcome === 'FAILED');
|
|
33
|
+
cli_1.default.log(color_1.default.green(`✅ ${created.length} created`) +
|
|
34
|
+
`, ${skipped.length} skipped (tag already exists)` +
|
|
35
|
+
(failed.length > 0 ? `, ${color_1.default.red(`${failed.length} failed`)}` : ''));
|
|
36
|
+
for (const entry of failed) {
|
|
37
|
+
cli_1.default.error(`${entry.resourceType.toLowerCase()} ${entry.slug} — ${entry.error ?? 'unknown error'}`);
|
|
38
|
+
}
|
|
39
|
+
return failed.length;
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const INHERITED_BLOCK_START = "# BEGIN fcode inherited resources (auto-generated, do not edit)";
|
|
2
2
|
export declare const INHERITED_BLOCK_END = "# END fcode inherited resources";
|
|
3
|
+
export declare const INHERITED_DEPENDENCY_FILES: string[];
|
|
3
4
|
/**
|
|
4
5
|
* Rebuilds the managed "inherited resources" block in a .gitignore.
|
|
5
6
|
*
|
|
@@ -14,6 +15,7 @@ export declare function renderGitignoreWithInherited(existing: string, inherited
|
|
|
14
15
|
* parent team and pulled read-only) are not tracked by git. Regenerated from the
|
|
15
16
|
* current workspace metadata, so resources that stop being inherited drop out.
|
|
16
17
|
* Inherited variable values live in one file, which is ignored as soon as the
|
|
17
|
-
* workspace inherits any variable; inherited locales are one file each, like modules
|
|
18
|
+
* workspace inherits any variable; inherited locales are one file each, like modules;
|
|
19
|
+
* inherited dependencies are one file per language, like variables.
|
|
18
20
|
*/
|
|
19
21
|
export declare function syncInheritedGitignore(workspaceDir?: string): Promise<void>;
|
package/dist/utils/gitignore.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.INHERITED_BLOCK_END = exports.INHERITED_BLOCK_START = void 0;
|
|
3
|
+
exports.INHERITED_DEPENDENCY_FILES = exports.INHERITED_BLOCK_END = exports.INHERITED_BLOCK_START = void 0;
|
|
4
4
|
exports.renderGitignoreWithInherited = renderGitignoreWithInherited;
|
|
5
5
|
exports.syncInheritedGitignore = syncInheritedGitignore;
|
|
6
6
|
const path = require("node:path");
|
|
@@ -8,10 +8,14 @@ const fs = require("fs-extra");
|
|
|
8
8
|
const workspace_1 = require("../config/workspace");
|
|
9
9
|
const global_1 = require("../config/global");
|
|
10
10
|
const settings_1 = require("../config/settings");
|
|
11
|
+
const programming_language_manifest_1 = require("../repositories/programming-language-manifest");
|
|
11
12
|
const filenamify_1 = require("./filenamify");
|
|
12
13
|
const file_utils_1 = require("./file-utils");
|
|
13
14
|
exports.INHERITED_BLOCK_START = '# BEGIN fcode inherited resources (auto-generated, do not edit)';
|
|
14
15
|
exports.INHERITED_BLOCK_END = '# END fcode inherited resources';
|
|
16
|
+
// Derived from the repository that writes them, so renaming a file cannot silently stop it being
|
|
17
|
+
// gitignored.
|
|
18
|
+
exports.INHERITED_DEPENDENCY_FILES = Object.values(programming_language_manifest_1.INHERITED_FILE_NAME).map((name) => `${programming_language_manifest_1.DEPENDENCIES_DIR}/${name}`);
|
|
15
19
|
/**
|
|
16
20
|
* Rebuilds the managed "inherited resources" block in a .gitignore.
|
|
17
21
|
*
|
|
@@ -46,7 +50,8 @@ function stripManagedBlock(content) {
|
|
|
46
50
|
* parent team and pulled read-only) are not tracked by git. Regenerated from the
|
|
47
51
|
* current workspace metadata, so resources that stop being inherited drop out.
|
|
48
52
|
* Inherited variable values live in one file, which is ignored as soon as the
|
|
49
|
-
* workspace inherits any variable; inherited locales are one file each, like modules
|
|
53
|
+
* workspace inherits any variable; inherited locales are one file each, like modules;
|
|
54
|
+
* inherited dependencies are one file per language, like variables.
|
|
50
55
|
*/
|
|
51
56
|
async function syncInheritedGitignore(workspaceDir = process.cwd()) {
|
|
52
57
|
const team = await new workspace_1.default(workspaceDir).getActiveTeam();
|
|
@@ -62,6 +67,10 @@ async function syncInheritedGitignore(workspaceDir = process.cwd()) {
|
|
|
62
67
|
...Object.values(team.i18n ?? {})
|
|
63
68
|
.filter((locale) => locale.resolvingTeamSlug)
|
|
64
69
|
.map((locale) => `i18n/${(0, filenamify_1.default)(locale.slug)}.yaml`),
|
|
70
|
+
// Inherited dependencies are one file per language, written only when there is something to
|
|
71
|
+
// inherit — checked on disk because the manifest metadata tracks the workspace's own list, not
|
|
72
|
+
// what its parents contribute.
|
|
73
|
+
...exports.INHERITED_DEPENDENCY_FILES.filter((file) => fs.existsSync(path.join(workspaceDir, file))),
|
|
65
74
|
];
|
|
66
75
|
const gitignorePath = path.join(workspaceDir, '.gitignore');
|
|
67
76
|
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
package/oclif.manifest.json
CHANGED
|
@@ -1675,11 +1675,19 @@
|
|
|
1675
1675
|
"team:pull": {
|
|
1676
1676
|
"aliases": [],
|
|
1677
1677
|
"args": {},
|
|
1678
|
-
"description": "Fetch team settings (timezone, error handler, inheritance) from cloud into team.json",
|
|
1678
|
+
"description": "Fetch team settings (timezone, error handler, inheritance) and team versions/aliases from cloud into team.json",
|
|
1679
1679
|
"examples": [
|
|
1680
1680
|
"$ fcode team:pull"
|
|
1681
1681
|
],
|
|
1682
|
-
"flags": {
|
|
1682
|
+
"flags": {
|
|
1683
|
+
"force": {
|
|
1684
|
+
"char": "f",
|
|
1685
|
+
"description": "Force command",
|
|
1686
|
+
"name": "force",
|
|
1687
|
+
"allowNo": false,
|
|
1688
|
+
"type": "boolean"
|
|
1689
|
+
}
|
|
1690
|
+
},
|
|
1683
1691
|
"hasDynamicHelp": false,
|
|
1684
1692
|
"hiddenAliases": [],
|
|
1685
1693
|
"id": "team:pull",
|
|
@@ -1698,7 +1706,7 @@
|
|
|
1698
1706
|
"team:push": {
|
|
1699
1707
|
"aliases": [],
|
|
1700
1708
|
"args": {},
|
|
1701
|
-
"description": "Update cloud team settings (timezone, error handler, inheritance, webhook auth) from team.json",
|
|
1709
|
+
"description": "Update cloud team settings (timezone, error handler, inheritance, webhook auth) and sync team versions/aliases from team.json",
|
|
1702
1710
|
"examples": [
|
|
1703
1711
|
"$ fcode team:push"
|
|
1704
1712
|
],
|
|
@@ -1729,7 +1737,7 @@
|
|
|
1729
1737
|
"team:status": {
|
|
1730
1738
|
"aliases": [],
|
|
1731
1739
|
"args": {},
|
|
1732
|
-
"description": "Show whether team settings (timezone, error handler, inheritance) differ from the cloud",
|
|
1740
|
+
"description": "Show whether team settings (timezone, error handler, inheritance) or team versions/aliases differ from the cloud",
|
|
1733
1741
|
"examples": [
|
|
1734
1742
|
"$ fcode team:status"
|
|
1735
1743
|
],
|
|
@@ -2163,5 +2171,5 @@
|
|
|
2163
2171
|
]
|
|
2164
2172
|
}
|
|
2165
2173
|
},
|
|
2166
|
-
"version": "2.
|
|
2174
|
+
"version": "2.4.0"
|
|
2167
2175
|
}
|