@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.
Files changed (46) hide show
  1. package/README.md +12 -8
  2. package/dist/commands/clone.js +14 -1
  3. package/dist/commands/dependencies/status.d.ts +11 -0
  4. package/dist/commands/dependencies/status.js +43 -0
  5. package/dist/commands/pull.js +1 -1
  6. package/dist/commands/team/pull.d.ts +3 -0
  7. package/dist/commands/team/pull.js +15 -4
  8. package/dist/commands/team/push.d.ts +2 -0
  9. package/dist/commands/team/push.js +55 -4
  10. package/dist/commands/team/status.js +8 -2
  11. package/dist/commands/team/versions/create.d.ts +0 -1
  12. package/dist/commands/team/versions/create.js +2 -11
  13. package/dist/core/commands/api-client-command.d.ts +1 -0
  14. package/dist/core/commands/api-client-command.js +5 -0
  15. package/dist/core/commands/base-command.d.ts +2 -0
  16. package/dist/core/commands/base-command.js +31 -0
  17. package/dist/core/dependencies/env-dependencies-command.js +6 -3
  18. package/dist/core/errors.d.ts +4 -0
  19. package/dist/core/errors.js +16 -1
  20. package/dist/core/executors/javascript-execution-task.js +14 -5
  21. package/dist/core/types.d.ts +29 -2
  22. package/dist/core/types.js +30 -2
  23. package/dist/core/utils/dependencies.js +4 -1
  24. package/dist/env.js +1 -1
  25. package/dist/repositories/process.js +4 -0
  26. package/dist/repositories/programming-language-manifest.d.ts +17 -1
  27. package/dist/repositories/programming-language-manifest.js +51 -1
  28. package/dist/repositories/team.d.ts +13 -3
  29. package/dist/repositories/team.js +51 -8
  30. package/dist/runtime/libs/python/fcode/context.py +15 -8
  31. package/dist/services/auth-service.d.ts +1 -0
  32. package/dist/services/auth-service.js +12 -0
  33. package/dist/services/fcode-graphql-client.d.ts +3 -0
  34. package/dist/services/fcode-graphql-client.js +97 -9
  35. package/dist/services/locale-merger.d.ts +1 -1
  36. package/dist/services/locale-merger.js +16 -13
  37. package/dist/services/locale-resolver.d.ts +6 -1
  38. package/dist/services/locale-resolver.js +11 -4
  39. package/dist/services/team-access.d.ts +13 -0
  40. package/dist/services/team-access.js +45 -0
  41. package/dist/services/team-versioning.d.ts +16 -0
  42. package/dist/services/team-versioning.js +40 -0
  43. package/dist/utils/gitignore.d.ts +3 -1
  44. package/dist/utils/gitignore.js +11 -2
  45. package/oclif.manifest.json +13 -5
  46. 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
- const merged = (0, locale_merger_1.mergeInheritedLocales)(locales);
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
- if (error.response && error.response.errors && error.response.errors[0] && error.response.errors[0].message.includes('isNative')) {
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
- const dependencies = _.map(allDependencies, (dependency) => {
920
- if (!dependency.isNative) {
921
- return { name: dependency.name, specifier: dependency.gitUrl ?? dependency.semVer };
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 null;
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 && b && a.name.localeCompare(b.name));
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: { dependencies: programmingLanguageManifest.dependencies },
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) => (files.length === 1 ? files[0] : mergeLocaleFiles(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 sorted by their slug — `Team.parentTeamIdsInResolutionOrder` — and
37
- * the first defining a key wins. Deriving that order here, rather than trusting the order the API
38
- * happened to list the rows in (which follows the listing's `orderBy`, `updatedAt` by default),
39
- * is what keeps a local run and the cloud resolving the same text.
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
- return [...files].sort((a, b) => {
43
- const [aSlug, bSlug] = [a.resolvingTeamSlug ?? '', b.resolvingTeamSlug ?? ''];
44
- return aSlug < bSlug ? -1 : (aSlug > bSlug ? 1 : 0);
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
- const fallback = primary && primary !== locale ? readLocale(workspaceDir, primary) : {};
119
- return { locale, translations: { ...fallback, ...readLocale(workspaceDir, locale) } };
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>;
@@ -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') : '';
@@ -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.2.0"
2174
+ "version": "2.4.0"
2167
2175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factorialco/fcode-cli",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "packageManager": "pnpm@11.7.0",
5
5
  "description": "Factorial Code Command Line Interface",
6
6
  "homepage": "https://code.factorialhr.com/docs/cli/",