@factorialco/fcode-cli 2.2.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/clone.js +14 -1
- 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/commands/api-client-command.d.ts +1 -0
- package/dist/core/commands/api-client-command.js +5 -0
- package/dist/core/commands/base-command.d.ts +2 -0
- package/dist/core/commands/base-command.js +31 -0
- package/dist/core/dependencies/env-dependencies-command.js +6 -3
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.js +16 -1
- package/dist/core/executors/javascript-execution-task.js +14 -5
- package/dist/core/types.d.ts +29 -2
- package/dist/core/types.js +30 -2
- 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/auth-service.d.ts +1 -0
- package/dist/services/auth-service.js +12 -0
- package/dist/services/fcode-graphql-client.d.ts +3 -0
- package/dist/services/fcode-graphql-client.js +97 -9
- package/dist/services/locale-merger.d.ts +1 -1
- package/dist/services/locale-merger.js +16 -13
- package/dist/services/locale-resolver.d.ts +6 -1
- package/dist/services/locale-resolver.js +11 -4
- package/dist/services/team-access.d.ts +13 -0
- package/dist/services/team-access.js +45 -0
- 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
|
@@ -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
|
}
|
|
@@ -90,6 +101,11 @@ class FcodeGraphQLClient {
|
|
|
90
101
|
},
|
|
91
102
|
});
|
|
92
103
|
}
|
|
104
|
+
// Swap the bearer token on the live client (after a session refresh) so every
|
|
105
|
+
// holder of this instance — repositories, pushers, queries — uses the new one.
|
|
106
|
+
setAccessToken(accessToken) {
|
|
107
|
+
this.client.setHeader('authorization', `Bearer ${accessToken}`);
|
|
108
|
+
}
|
|
93
109
|
isDuplicatedError(error) {
|
|
94
110
|
return (error &&
|
|
95
111
|
error.response &&
|
|
@@ -345,7 +361,10 @@ class FcodeGraphQLClient {
|
|
|
345
361
|
catch (error) {
|
|
346
362
|
return Promise.reject(this.cleanError(error));
|
|
347
363
|
}
|
|
348
|
-
|
|
364
|
+
// The cloud team's parent order is the inheritance precedence the merge must replicate; the
|
|
365
|
+
// local team.json may lag behind it, so the cloud is asked, not the file.
|
|
366
|
+
const { parentTeamSlugs } = await this.team();
|
|
367
|
+
const merged = (0, locale_merger_1.mergeInheritedLocales)(locales, parentTeamSlugs ?? []);
|
|
349
368
|
(0, locale_merger_1.assertLocaleFilesCanCoexist)(merged);
|
|
350
369
|
return merged;
|
|
351
370
|
}
|
|
@@ -421,6 +440,21 @@ class FcodeGraphQLClient {
|
|
|
421
440
|
settings.formSettings.authMode = process.form.authMode;
|
|
422
441
|
}
|
|
423
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
|
+
}
|
|
424
458
|
const input = {
|
|
425
459
|
name: process.name,
|
|
426
460
|
parametersSchema: process.script.parametersSchema,
|
|
@@ -637,6 +671,13 @@ class FcodeGraphQLClient {
|
|
|
637
671
|
isNative
|
|
638
672
|
gitUrl
|
|
639
673
|
}
|
|
674
|
+
inheritedDependencies {
|
|
675
|
+
name
|
|
676
|
+
semVer
|
|
677
|
+
isNative
|
|
678
|
+
gitUrl
|
|
679
|
+
resolvingTeamSlug
|
|
680
|
+
}
|
|
640
681
|
}
|
|
641
682
|
}
|
|
642
683
|
`, { programmingLanguage })
|
|
@@ -646,7 +687,10 @@ class FcodeGraphQLClient {
|
|
|
646
687
|
.catch((error) => {
|
|
647
688
|
// TODO: This is a workaround backward compatibility with old versions of fcode-api.
|
|
648
689
|
// Remove when full on-premises installations are updated.
|
|
649
|
-
|
|
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))) {
|
|
650
694
|
return this.client
|
|
651
695
|
.request((0, graphql_request_1.gql) `
|
|
652
696
|
query getProgrammingLanguageManifest($programmingLanguage: ProgrammingLanguage!) {
|
|
@@ -724,6 +768,21 @@ class FcodeGraphQLClient {
|
|
|
724
768
|
metadata.form.authMode = settings.formSettings.authMode;
|
|
725
769
|
}
|
|
726
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
|
+
}
|
|
727
786
|
return new types_1.Process(id, slug, script, readme, { dependencies }, settings, metadata, this.inheritedTeam(resolvingTeamSlug));
|
|
728
787
|
}
|
|
729
788
|
/**
|
|
@@ -860,6 +919,23 @@ class FcodeGraphQLClient {
|
|
|
860
919
|
return Promise.reject(this.cleanError(error));
|
|
861
920
|
});
|
|
862
921
|
}
|
|
922
|
+
// Cheapest team-scoped query, used as a pre-flight access check so commands can
|
|
923
|
+
// fail once with a clear error before touching local state, instead of erroring
|
|
924
|
+
// on every resource pull.
|
|
925
|
+
verifyTeamAccess() {
|
|
926
|
+
return this.client
|
|
927
|
+
.request((0, graphql_request_1.gql) `
|
|
928
|
+
query team($slug: ID!) {
|
|
929
|
+
team(slug: $slug) {
|
|
930
|
+
slug
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
`, { slug: this.teamSlug })
|
|
934
|
+
.then(() => undefined)
|
|
935
|
+
.catch((error) => {
|
|
936
|
+
return Promise.reject(this.cleanError(error));
|
|
937
|
+
});
|
|
938
|
+
}
|
|
863
939
|
team() {
|
|
864
940
|
return this.client
|
|
865
941
|
.request((0, graphql_request_1.gql) `
|
|
@@ -916,15 +992,23 @@ class FcodeGraphQLClient {
|
|
|
916
992
|
}
|
|
917
993
|
mapProgrammingLanguageManifest(programmingLanguageManifest) {
|
|
918
994
|
const allDependencies = programmingLanguageManifest.nextInstallation?.dependencies || programmingLanguageManifest.dependencies;
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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;
|
|
922
1003
|
}
|
|
923
|
-
return
|
|
1004
|
+
return {
|
|
1005
|
+
name: dependency.name,
|
|
1006
|
+
specifier: dependency.gitUrl ?? dependency.semVer,
|
|
1007
|
+
...(dependency.resolvingTeamSlug && { resolvingTeamSlug: dependency.resolvingTeamSlug }),
|
|
1008
|
+
};
|
|
924
1009
|
})
|
|
925
1010
|
.filter(Boolean)
|
|
926
|
-
.sort((a, b) => a
|
|
927
|
-
return new types_1.ProgrammingLanguageManifest(programmingLanguageManifest.id, programmingLanguageManifest.programmingLanguage, programmingLanguageManifest.programmingLanguage, dependencies);
|
|
1011
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
928
1012
|
}
|
|
929
1013
|
allProgrammingLanguageManifest() {
|
|
930
1014
|
const result = [];
|
|
@@ -958,7 +1042,11 @@ class FcodeGraphQLClient {
|
|
|
958
1042
|
}
|
|
959
1043
|
`, {
|
|
960
1044
|
id: programmingLanguageManifest.id,
|
|
961
|
-
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
|
+
},
|
|
962
1050
|
})
|
|
963
1051
|
.then(({ updateProgrammingLanguageManifest: programmingLanguageManifest }) => {
|
|
964
1052
|
return this.client
|
|
@@ -11,7 +11,7 @@ import { Locale } from '../core/types';
|
|
|
11
11
|
* several parents define is rewritten, as a flat mapping of dotted keys, which is the shape the
|
|
12
12
|
* lookup uses anyway and the one shape where a nested and a dotted key cannot disagree.
|
|
13
13
|
*/
|
|
14
|
-
export declare function mergeInheritedLocales(locales: Array<Locale>): Array<Locale>;
|
|
14
|
+
export declare function mergeInheritedLocales(locales: Array<Locale>, parentTeamSlugs: Array<string>): Array<Locale>;
|
|
15
15
|
/**
|
|
16
16
|
* Locale identifiers are case-sensitive on the platform (pt-BR and pt-br are different locales),
|
|
17
17
|
* but the filesystems macOS and Windows usually run on are not — both identifiers would collapse
|
|
@@ -22,34 +22,37 @@ const locale_resolver_1 = require("./locale-resolver");
|
|
|
22
22
|
* several parents define is rewritten, as a flat mapping of dotted keys, which is the shape the
|
|
23
23
|
* lookup uses anyway and the one shape where a nested and a dotted key cannot disagree.
|
|
24
24
|
*/
|
|
25
|
-
function mergeInheritedLocales(locales) {
|
|
25
|
+
function mergeInheritedLocales(locales, parentTeamSlugs) {
|
|
26
26
|
const inheritedByIdentifier = new Map();
|
|
27
27
|
for (const locale of locales.filter((locale) => locale.resolvingTeamSlug)) {
|
|
28
28
|
inheritedByIdentifier.set(locale.locale, [...(inheritedByIdentifier.get(locale.locale) ?? []), locale]);
|
|
29
29
|
}
|
|
30
30
|
return [
|
|
31
31
|
...locales.filter((locale) => !locale.resolvingTeamSlug),
|
|
32
|
-
...[...inheritedByIdentifier.values()].map((files) =>
|
|
32
|
+
...[...inheritedByIdentifier.values()].map((files) => files.length === 1 ? files[0] : mergeLocaleFiles(files, parentTeamSlugs)),
|
|
33
33
|
];
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* The platform walks parents
|
|
37
|
-
* the first defining a key wins. Deriving that order here
|
|
38
|
-
* happened to list the rows in (which
|
|
39
|
-
* is what keeps a local run and the
|
|
36
|
+
* The platform walks parents in the team's configured order — `Team.parentTeamIdsInResolutionOrder`
|
|
37
|
+
* — and the first defining a key wins. Deriving that order here from the cloud team's
|
|
38
|
+
* `parentTeamSlugs`, rather than trusting the order the API happened to list the rows in (which
|
|
39
|
+
* follows the listing's `orderBy`, `updatedAt` by default), is what keeps a local run and the
|
|
40
|
+
* cloud resolving the same text.
|
|
40
41
|
*/
|
|
41
|
-
function inResolutionOrder(files) {
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
return
|
|
45
|
-
}
|
|
42
|
+
function inResolutionOrder(files, parentTeamSlugs) {
|
|
43
|
+
const precedence = (file) => {
|
|
44
|
+
const index = parentTeamSlugs.indexOf(file.resolvingTeamSlug ?? '');
|
|
45
|
+
return index === -1 ? parentTeamSlugs.length : index;
|
|
46
|
+
};
|
|
47
|
+
return [...files].sort((a, b) => precedence(a) - precedence(b));
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
48
50
|
* @param files the same locale as every parent workspace defines it, in any order
|
|
51
|
+
* @param parentTeamSlugs the workspace's parents in inheritance-precedence order
|
|
49
52
|
* @returns one locale carrying their merged translations, attributed to the winning parent
|
|
50
53
|
*/
|
|
51
|
-
function mergeLocaleFiles(files) {
|
|
52
|
-
const ordered = inResolutionOrder(files);
|
|
54
|
+
function mergeLocaleFiles(files, parentTeamSlugs) {
|
|
55
|
+
const ordered = inResolutionOrder(files, parentTeamSlugs);
|
|
53
56
|
const [winner] = ordered;
|
|
54
57
|
const translations = {};
|
|
55
58
|
// Lowest precedence first, so the first parent in resolution order is the text that survives —
|
|
@@ -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,13 @@
|
|
|
1
|
+
import FcodeGraphQLClient from './fcode-graphql-client';
|
|
2
|
+
import GlobalConfig from '../config/global';
|
|
3
|
+
declare const TeamAccessService: {
|
|
4
|
+
globalConfig: GlobalConfig;
|
|
5
|
+
clientFactory: (options: {
|
|
6
|
+
url: string;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
teamSlug: string;
|
|
9
|
+
}) => FcodeGraphQLClient;
|
|
10
|
+
ensureTeamAccess(url: string, slug: string): Promise<void>;
|
|
11
|
+
checkAccess(url: string, slug: string): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export default TeamAccessService;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const auth_service_1 = require("./auth-service");
|
|
4
|
+
const fcode_graphql_client_1 = require("./fcode-graphql-client");
|
|
5
|
+
const global_1 = require("../config/global");
|
|
6
|
+
const errors_1 = require("../core/errors");
|
|
7
|
+
function is403(error) {
|
|
8
|
+
return error?.response?.status === 403;
|
|
9
|
+
}
|
|
10
|
+
const TeamAccessService = {
|
|
11
|
+
globalConfig: new global_1.default(),
|
|
12
|
+
clientFactory: (options) => new fcode_graphql_client_1.default(options),
|
|
13
|
+
// Pre-flight check that the current session can access the team, run before any
|
|
14
|
+
// local state is touched so a denied session fails once with a clear error
|
|
15
|
+
// instead of erroring on every resource pull. A 403 may just mean the token
|
|
16
|
+
// predates the team: groups are snapshotted into the token at issuance
|
|
17
|
+
// (FCT-61231), so re-mint it through the refresh grant and retry once.
|
|
18
|
+
async ensureTeamAccess(url, slug) {
|
|
19
|
+
try {
|
|
20
|
+
await this.checkAccess(url, slug);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!is403(error)) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
if (!(await auth_service_1.default.refreshSession(url))) {
|
|
27
|
+
throw new errors_1.TeamAccessDenied(slug);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await this.checkAccess(url, slug);
|
|
31
|
+
}
|
|
32
|
+
catch (retryError) {
|
|
33
|
+
if (is403(retryError)) {
|
|
34
|
+
throw new errors_1.TeamAccessDenied(slug);
|
|
35
|
+
}
|
|
36
|
+
throw retryError;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async checkAccess(url, slug) {
|
|
41
|
+
const { token } = await this.globalConfig.getAuthInfo(url);
|
|
42
|
+
await this.clientFactory({ url, accessToken: token.access_token, teamSlug: slug }).verifyTeamAccess();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
exports.default = TeamAccessService;
|
|
@@ -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
|
}
|