@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
package/dist/core/types.d.ts
CHANGED
|
@@ -130,12 +130,30 @@ export interface ProcessForm {
|
|
|
130
130
|
appRole?: AppRole;
|
|
131
131
|
authMode?: FormAuthMode;
|
|
132
132
|
}
|
|
133
|
+
export interface ProcessUiTrigger {
|
|
134
|
+
enabled: boolean;
|
|
135
|
+
/** Identifier of the UI location the trigger is rendered in. */
|
|
136
|
+
locationId?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Label shown on the trigger. May contain fcode.i18n("key") tokens, which the platform
|
|
139
|
+
* resolves when a process listing names a locale.
|
|
140
|
+
*/
|
|
141
|
+
label?: string;
|
|
142
|
+
/** f0 icon name, e.g. "Globe". */
|
|
143
|
+
icon?: string;
|
|
144
|
+
/**
|
|
145
|
+
* When true the trigger executes the process synchronously and returns the result
|
|
146
|
+
* envelope; otherwise the execution is fire-and-forget.
|
|
147
|
+
*/
|
|
148
|
+
awaitResult?: boolean;
|
|
149
|
+
}
|
|
133
150
|
export interface ProcessMetadataFields {
|
|
134
151
|
name: string;
|
|
135
152
|
description?: string;
|
|
136
153
|
tags: string[];
|
|
137
154
|
webhook?: ProcessWebhook;
|
|
138
155
|
form?: ProcessForm;
|
|
156
|
+
uiTrigger?: ProcessUiTrigger;
|
|
139
157
|
}
|
|
140
158
|
export declare class Process extends Resource {
|
|
141
159
|
id: string;
|
|
@@ -146,6 +164,7 @@ export declare class Process extends Resource {
|
|
|
146
164
|
tags: string[];
|
|
147
165
|
webhook?: ProcessWebhook;
|
|
148
166
|
form?: ProcessForm;
|
|
167
|
+
uiTrigger?: ProcessUiTrigger;
|
|
149
168
|
script: {
|
|
150
169
|
parametersSchema: any;
|
|
151
170
|
programmingLanguage: ProgrammingLanguage;
|
|
@@ -238,7 +257,14 @@ export declare class ProgrammingLanguageManifest extends Resource {
|
|
|
238
257
|
slug: string;
|
|
239
258
|
programmingLanguage: ProgrammingLanguage;
|
|
240
259
|
dependencies: Array<Dependency>;
|
|
241
|
-
|
|
260
|
+
inheritedDependencies: Array<Dependency>;
|
|
261
|
+
constructor(id: string, slug: string, programmingLanguage: ProgrammingLanguage, dependencies: Array<Dependency>, inheritedDependencies?: Array<Dependency>);
|
|
262
|
+
/**
|
|
263
|
+
* What an execution installs: this workspace's dependencies layered over the inherited ones, one
|
|
264
|
+
* entry per package name. Mirrors the platform's merge so a local run resolves the same install
|
|
265
|
+
* directory the cloud does.
|
|
266
|
+
*/
|
|
267
|
+
get effectiveDependencies(): Array<Dependency>;
|
|
242
268
|
get contentHash(): string;
|
|
243
269
|
}
|
|
244
270
|
export interface ErrorHandlerConfig {
|
|
@@ -273,7 +299,7 @@ export interface TeamVersionAlias {
|
|
|
273
299
|
export interface TeamVersionFileEntry {
|
|
274
300
|
tag: string;
|
|
275
301
|
comment?: string;
|
|
276
|
-
createdAt
|
|
302
|
+
createdAt?: string;
|
|
277
303
|
}
|
|
278
304
|
export interface TeamAliasFileEntry {
|
|
279
305
|
name: string;
|
|
@@ -363,6 +389,7 @@ export declare const DependenciesFlags: {
|
|
|
363
389
|
export declare class Dependency {
|
|
364
390
|
name: string;
|
|
365
391
|
specifier: string;
|
|
392
|
+
resolvingTeamSlug?: string;
|
|
366
393
|
}
|
|
367
394
|
export declare class ServiceAccount extends Resource {
|
|
368
395
|
id: string;
|
package/dist/core/types.js
CHANGED
|
@@ -49,6 +49,7 @@ class Process extends Resource {
|
|
|
49
49
|
this.tags = metadata.tags;
|
|
50
50
|
this.webhook = metadata.webhook;
|
|
51
51
|
this.form = metadata.form;
|
|
52
|
+
this.uiTrigger = metadata.uiTrigger;
|
|
52
53
|
this.script = script;
|
|
53
54
|
this.readme = readme;
|
|
54
55
|
this.manifest = settings.dependencies.scopedToProcess && manifest !== null ? manifest : { dependencies: [] };
|
|
@@ -65,6 +66,9 @@ class Process extends Resource {
|
|
|
65
66
|
tags: this.tags || [],
|
|
66
67
|
webhook: this.webhook || null,
|
|
67
68
|
form: this.form || null,
|
|
69
|
+
// Inserted only when set, so every process that predates UI triggers keeps the
|
|
70
|
+
// content hash already stored in .fcode (mirrors Team.contentHash).
|
|
71
|
+
...(this.uiTrigger && { uiTrigger: this.uiTrigger }),
|
|
68
72
|
manifestDependencies: this.manifest.dependencies,
|
|
69
73
|
dependenciesScopedToProcess: this.settings.dependencies.scopedToProcess,
|
|
70
74
|
});
|
|
@@ -185,12 +189,22 @@ exports.Variable = Variable;
|
|
|
185
189
|
Variable.ENV_KEY_MAX_LENGTH = 64;
|
|
186
190
|
Variable.ENV_KEY_REGEX = /^[A-Z_a-z]\w*$/;
|
|
187
191
|
class ProgrammingLanguageManifest extends Resource {
|
|
188
|
-
constructor(id, slug, programmingLanguage, dependencies) {
|
|
192
|
+
constructor(id, slug, programmingLanguage, dependencies, inheritedDependencies = []) {
|
|
189
193
|
super();
|
|
190
194
|
this.id = id;
|
|
191
195
|
this.slug = slug;
|
|
192
196
|
this.programmingLanguage = programmingLanguage;
|
|
193
197
|
this.dependencies = dependencies;
|
|
198
|
+
this.inheritedDependencies = inheritedDependencies;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* What an execution installs: this workspace's dependencies layered over the inherited ones, one
|
|
202
|
+
* entry per package name. Mirrors the platform's merge so a local run resolves the same install
|
|
203
|
+
* directory the cloud does.
|
|
204
|
+
*/
|
|
205
|
+
get effectiveDependencies() {
|
|
206
|
+
const ownNames = new Set(this.dependencies.map((dependency) => dependency.name));
|
|
207
|
+
return [...this.dependencies, ...this.inheritedDependencies.filter((dependency) => !ownNames.has(dependency.name))];
|
|
194
208
|
}
|
|
195
209
|
get contentHash() {
|
|
196
210
|
return crypto
|
|
@@ -214,7 +228,9 @@ class Team extends Resource {
|
|
|
214
228
|
const payload = {
|
|
215
229
|
zoneId: this.zoneId || null,
|
|
216
230
|
errorHandlerConfig: this.errorHandlerConfig || null,
|
|
217
|
-
|
|
231
|
+
// Kept in the given order, not sorted: the order is the inheritance precedence, so a pure
|
|
232
|
+
// reorder is a real change that must be detected and pushed.
|
|
233
|
+
parentTeamSlugs: [...(this.parentTeamSlugs || [])],
|
|
218
234
|
};
|
|
219
235
|
// Inserted only when set, so every team.json that predates webhook auth keeps the
|
|
220
236
|
// hash already stored in .fcode and does not report a phantom conflict. A key present
|
|
@@ -227,6 +243,18 @@ class Team extends Resource {
|
|
|
227
243
|
if (this.primaryLocale) {
|
|
228
244
|
payload.primaryLocale = this.primaryLocale;
|
|
229
245
|
}
|
|
246
|
+
// Versions hash only their tags and aliases their name->tag pairs: createdAt is
|
|
247
|
+
// server-assigned and a comment is immutable once published (there is no update mutation),
|
|
248
|
+
// so neither can represent a pushable local change. Inserted only when non-empty, again to
|
|
249
|
+
// keep hashes stored before versioning existed valid.
|
|
250
|
+
if (this.versions && this.versions.length > 0) {
|
|
251
|
+
payload.versions = this.versions.map(({ tag }) => tag).sort();
|
|
252
|
+
}
|
|
253
|
+
if (this.aliases && this.aliases.length > 0) {
|
|
254
|
+
payload.aliases = this.aliases
|
|
255
|
+
.map(({ name, tag }) => ({ name, tag }))
|
|
256
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
257
|
+
}
|
|
230
258
|
return (0, canonical_hash_1.canonicalJsonHash)(payload);
|
|
231
259
|
}
|
|
232
260
|
}
|
|
@@ -12,7 +12,10 @@ async function getDependenciesPath(programmingLanguage) {
|
|
|
12
12
|
const basePath = (0, workspace_1.baseDependenciesPath)(programmingLanguage);
|
|
13
13
|
const repo = new programming_language_manifest_1.default();
|
|
14
14
|
const programmingLanguageManifest = await repo.find(programmingLanguage);
|
|
15
|
-
|
|
15
|
+
// The effective set, not the workspace's own: the cloud installs the inherited dependencies too,
|
|
16
|
+
// and the directory is named after the hash of what is installed. Hashing only the own list here
|
|
17
|
+
// would make a local run resolve a different directory than the platform.
|
|
18
|
+
const dependenciesDirectory = (0, env_dependencies_command_1.dependenciesHash)(programmingLanguageManifest.effectiveDependencies);
|
|
16
19
|
return path.join(basePath, dependenciesDirectory);
|
|
17
20
|
}
|
|
18
21
|
function getProcessDependenciesPath(process) {
|
package/dist/env.js
CHANGED
|
@@ -14,7 +14,7 @@ const zod_1 = require("zod");
|
|
|
14
14
|
*/
|
|
15
15
|
exports.envSchema = zod_1.z.object({
|
|
16
16
|
// --- Cloud / auth endpoints ---
|
|
17
|
-
FCODE_CLOUD_URL: zod_1.z.string().min(1).default('https://code.
|
|
17
|
+
FCODE_CLOUD_URL: zod_1.z.string().min(1).default('https://code.factorialhr.com/platform'),
|
|
18
18
|
FCODE_AUTH_URL: zod_1.z.string().min(1).optional(),
|
|
19
19
|
FCODE_CLIENT_ID: zod_1.z.string().min(1).default('cli'),
|
|
20
20
|
// --- Local config location (defaults to os.homedir() at the call site) ---
|
|
@@ -136,6 +136,7 @@ class ProcessRepository extends resource_1.default {
|
|
|
136
136
|
tags: Array.isArray(raw.tags) ? raw.tags : [],
|
|
137
137
|
webhook: raw.webhook,
|
|
138
138
|
form: raw.form,
|
|
139
|
+
uiTrigger: raw.uiTrigger,
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
catch (error) {
|
|
@@ -156,6 +157,9 @@ class ProcessRepository extends resource_1.default {
|
|
|
156
157
|
if (process.form !== undefined) {
|
|
157
158
|
metadata.form = process.form;
|
|
158
159
|
}
|
|
160
|
+
if (process.uiTrigger !== undefined) {
|
|
161
|
+
metadata.uiTrigger = process.uiTrigger;
|
|
162
|
+
}
|
|
159
163
|
return metadata;
|
|
160
164
|
}
|
|
161
165
|
async findParameters(processSlug) {
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProgrammingLanguage, ProgrammingLanguageManifest, ResourceMetadata } from '../core/types';
|
|
2
2
|
import ResourceRepositoryBase from './resource';
|
|
3
|
+
/**
|
|
4
|
+
* The inherited file each language collapses its parents into. Exported so the gitignore sync names
|
|
5
|
+
* exactly what this repository writes, rather than repeating the strings.
|
|
6
|
+
*/
|
|
7
|
+
export declare const INHERITED_FILE_NAME: Record<ProgrammingLanguage, string>;
|
|
8
|
+
export declare const DEPENDENCIES_DIR = "dependencies";
|
|
3
9
|
declare class ProgrammingLanguageManifestRepository extends ResourceRepositoryBase<ProgrammingLanguageManifest> {
|
|
4
10
|
constructor();
|
|
5
11
|
find(slug: string): Promise<ProgrammingLanguageManifest>;
|
|
6
12
|
protected getSourceCodeFileName(programmingLanguageManifest: ProgrammingLanguageManifest): string;
|
|
13
|
+
/**
|
|
14
|
+
* The inherited dependencies of one language, collapsed into a single file the way inherited
|
|
15
|
+
* variables are (`variables.inherited.env`) rather than one file per parent: two versions of the
|
|
16
|
+
* same package cannot be installed side by side, so there is only ever one winner per name and
|
|
17
|
+
* the platform has already picked it.
|
|
18
|
+
*/
|
|
19
|
+
protected getInheritedFileName(programmingLanguage: ProgrammingLanguage): string;
|
|
20
|
+
getInheritedPath(programmingLanguage: ProgrammingLanguage): string;
|
|
21
|
+
private readInheritedDependencies;
|
|
7
22
|
protected getResourceDirectory(programmingLanguageManifest: ProgrammingLanguageManifest): string;
|
|
8
23
|
protected getResourcePath(programmingLanguageManifest: ProgrammingLanguageManifest): string;
|
|
9
24
|
protected removeFiles(programmingLanguageManifest: ProgrammingLanguageManifest): Promise<void>;
|
|
10
25
|
protected resourceToMetadata(programmingLanguageManifest: ProgrammingLanguageManifest): ResourceMetadata;
|
|
11
26
|
protected saveFiles(programmingLanguageManifest: ProgrammingLanguageManifest): Promise<void>;
|
|
27
|
+
private removeInheritedFile;
|
|
12
28
|
metadataFromFiles(slug: string): Promise<ResourceMetadata>;
|
|
13
29
|
validSlug(slug: string): boolean;
|
|
14
30
|
slugFormatMessage(): string;
|
|
@@ -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):
|
|
@@ -8,6 +8,7 @@ declare const AuthService: {
|
|
|
8
8
|
isAuthenticated(url: string): Promise<boolean>;
|
|
9
9
|
isTokenExpiring(token: Token): boolean;
|
|
10
10
|
refreshToken(authUrl: string, refreshToken: string): Promise<Token>;
|
|
11
|
+
refreshSession(url: string): Promise<boolean>;
|
|
11
12
|
tryRefresh(url: string, { authUrl, token, userInfo }: AuthInfo): Promise<boolean>;
|
|
12
13
|
login({ url, authUrl, email, password, clientId, clientSecret, accessToken }: Credentials): Promise<AuthInfo>;
|
|
13
14
|
logout(url: string): Promise<void>;
|
|
@@ -117,6 +117,18 @@ const AuthService = {
|
|
|
117
117
|
}
|
|
118
118
|
throw token.error_description;
|
|
119
119
|
},
|
|
120
|
+
// Force a re-mint of the access token regardless of expiry. Team groups are
|
|
121
|
+
// snapshotted into the token at issuance, so a token created before a team
|
|
122
|
+
// existed (or before the user joined it) 403s on it until re-minted (FCT-61231).
|
|
123
|
+
async refreshSession(url) {
|
|
124
|
+
try {
|
|
125
|
+
const authInfo = await this.globalConfig.getAuthInfo(url);
|
|
126
|
+
return await this.tryRefresh(url, authInfo);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
120
132
|
async tryRefresh(url, { authUrl, token, userInfo }) {
|
|
121
133
|
if (!token.refresh_token) {
|
|
122
134
|
return false;
|
|
@@ -10,6 +10,7 @@ declare class FcodeGraphQLClient {
|
|
|
10
10
|
private teamSlug;
|
|
11
11
|
private url;
|
|
12
12
|
constructor(options: FcodeGraphQLClientOptions);
|
|
13
|
+
setAccessToken(accessToken: string): void;
|
|
13
14
|
isDuplicatedError(error: any): boolean;
|
|
14
15
|
isEmptyValueError(error: any): boolean;
|
|
15
16
|
cleanError(error: any): any;
|
|
@@ -63,9 +64,11 @@ declare class FcodeGraphQLClient {
|
|
|
63
64
|
/** The API upserts: an existing alias with the same name is re-pointed to the tag. */
|
|
64
65
|
createTeamVersionAlias(name: string, tag: string): Promise<TeamVersionAlias>;
|
|
65
66
|
deleteTeamVersionAlias(id: string): Promise<TeamVersionAlias>;
|
|
67
|
+
verifyTeamAccess(): Promise<void>;
|
|
66
68
|
team(): Promise<RawTeamSettings>;
|
|
67
69
|
pushTeam(update: TeamUpdateInput): Promise<RawTeamSettings>;
|
|
68
70
|
mapProgrammingLanguageManifest(programmingLanguageManifest: any): ProgrammingLanguageManifest;
|
|
71
|
+
private mapDependencies;
|
|
69
72
|
allProgrammingLanguageManifest(): Promise<Array<ProgrammingLanguageManifest>>;
|
|
70
73
|
pushProgrammingLanguageManifest(programmingLanguageManifest: ProgrammingLanguageManifest): Promise<ProgrammingLanguageManifest>;
|
|
71
74
|
allServiceAccounts(): Promise<Array<ServiceAccount>>;
|