@factorialco/fcode-cli 1.5.0 → 2.1.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 +123 -3
- package/dist/commands/http.d.ts +6 -4
- package/dist/commands/http.js +40 -24
- package/dist/commands/setup-debug.js +2 -2
- package/dist/commands/team/aliases/delete.d.ts +9 -0
- package/dist/commands/team/aliases/delete.js +28 -0
- package/dist/commands/team/aliases/list.d.ts +6 -0
- package/dist/commands/team/aliases/list.js +23 -0
- package/dist/commands/team/aliases/set.d.ts +10 -0
- package/dist/commands/team/aliases/set.js +25 -0
- package/dist/commands/team/pull.js +8 -1
- package/dist/commands/team/push.js +9 -1
- package/dist/commands/team/versions/create.d.ts +13 -0
- package/dist/commands/team/versions/create.js +42 -0
- package/dist/commands/team/versions/delete.d.ts +12 -0
- package/dist/commands/team/versions/delete.js +32 -0
- package/dist/commands/team/versions/list.d.ts +6 -0
- package/dist/commands/team/versions/list.js +25 -0
- package/dist/commands/variables/status.js +11 -0
- package/dist/config/settings.d.ts +1 -0
- package/dist/config/settings.js +6 -0
- package/dist/core/types.d.ts +65 -4
- package/dist/core/types.js +16 -5
- package/dist/env.d.ts +2 -2
- package/dist/env.js +1 -1
- package/dist/repositories/team.d.ts +10 -2
- package/dist/repositories/team.js +53 -3
- package/dist/repositories/variable.d.ts +24 -0
- package/dist/repositories/variable.js +70 -22
- package/dist/runtime/executor.js +2 -2
- package/dist/runtime/executor.py +2 -2
- package/dist/runtime/libs/python/fcode/__init__.py +2 -2
- package/dist/services/auth-service.d.ts +3 -0
- package/dist/services/auth-service.js +56 -5
- package/dist/services/fcode-graphql-client.d.ts +20 -1
- package/dist/services/fcode-graphql-client.js +239 -49
- package/dist/services/oauth-browser-service.js +1 -0
- package/dist/services/webhook-auth.d.ts +21 -0
- package/dist/services/webhook-auth.js +77 -0
- package/dist/utils/gitignore.d.ts +2 -0
- package/dist/utils/gitignore.js +6 -0
- package/oclif.manifest.json +208 -17
- package/package.json +10 -2
package/dist/config/settings.js
CHANGED
|
@@ -8,6 +8,7 @@ var SettingKeys;
|
|
|
8
8
|
SettingKeys["IGNORE_PROCESS_VERSIONS"] = "ignoreProcessVersions";
|
|
9
9
|
SettingKeys["IGNORE_WORKSPACE_NOT_EMPTY"] = "ignoreWorkspaceNotEmpty";
|
|
10
10
|
SettingKeys["IGNORE_FCODE_RUN_PROCESSES"] = "ignoreFcodeRunProcesses";
|
|
11
|
+
SettingKeys["INHERITED_VARIABLES_FILE_NAME"] = "inheritedVariablesFileName";
|
|
11
12
|
SettingKeys["LOCAL_VARIABLES_FILE_NAME"] = "localVariablesFileName";
|
|
12
13
|
SettingKeys["SKIP_SKILLS_SETUP"] = "skipSkillsSetup";
|
|
13
14
|
SettingKeys["VARIABLES_FILE_NAME"] = "variablesFileName";
|
|
@@ -48,6 +49,11 @@ exports.SETTINGS_DEFINITIONS = [
|
|
|
48
49
|
default: 'variables.local.env',
|
|
49
50
|
description: 'Local variables file name',
|
|
50
51
|
},
|
|
52
|
+
{
|
|
53
|
+
key: SettingKeys.INHERITED_VARIABLES_FILE_NAME,
|
|
54
|
+
default: 'variables.inherited.env',
|
|
55
|
+
description: 'File holding the variables inherited from parent workspaces (read-only, gitignored)',
|
|
56
|
+
},
|
|
51
57
|
];
|
|
52
58
|
function getSettingDefault(key) {
|
|
53
59
|
return exports.SETTINGS_DEFINITIONS.find((def) => def.key === key)?.default;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type Credentials = {
|
|
|
10
10
|
export interface Token {
|
|
11
11
|
access_token: string;
|
|
12
12
|
refresh_token: string;
|
|
13
|
+
expires_at?: number;
|
|
13
14
|
}
|
|
14
15
|
export interface UserInfo {
|
|
15
16
|
groups: string[];
|
|
@@ -98,15 +99,33 @@ export type MergedResource = MergedResourceRecord & {
|
|
|
98
99
|
};
|
|
99
100
|
export type ProgrammingLanguage = 'JAVASCRIPT' | 'PYTHON';
|
|
100
101
|
export declare const programmingLanguageDisplayName: (programmingLanguage: ProgrammingLanguage) => string;
|
|
102
|
+
export type WebhookAuthMode = 'NONE' | 'TEAM' | 'CUSTOM';
|
|
103
|
+
export declare const DEFAULT_WEBHOOK_AUTH_HEADER = "Authorization";
|
|
104
|
+
export interface WebhookAuth {
|
|
105
|
+
/**
|
|
106
|
+
* Header the caller must send the credential in. Omitted when it is the default
|
|
107
|
+
* `Authorization`, whose value must be `Bearer <token>`; any other header takes the raw value.
|
|
108
|
+
*/
|
|
109
|
+
headerName?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Key of the team variable holding the expected value. Only the name travels, never the
|
|
112
|
+
* value, and the variable does not need to exist yet.
|
|
113
|
+
*/
|
|
114
|
+
variableKey: string;
|
|
115
|
+
}
|
|
101
116
|
export interface ProcessWebhook {
|
|
102
117
|
enabled: boolean;
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
/** Omitted when the webhook is public, so an unprotected webhook stays minimal on disk. */
|
|
119
|
+
authMode?: WebhookAuthMode;
|
|
120
|
+
/** The webhook's own configuration, only meaningful with the CUSTOM mode. */
|
|
121
|
+
auth?: WebhookAuth;
|
|
105
122
|
}
|
|
106
123
|
export type AppRole = 'INSTALL' | 'SETTINGS' | 'USER_FACING_FORM' | 'UNINSTALL' | 'NONE';
|
|
124
|
+
export type FormAuthMode = 'NONE' | 'FACTORIAL';
|
|
107
125
|
export interface ProcessForm {
|
|
108
126
|
enabled: boolean;
|
|
109
127
|
appRole?: AppRole;
|
|
128
|
+
authMode?: FormAuthMode;
|
|
110
129
|
}
|
|
111
130
|
export interface ProcessMetadataFields {
|
|
112
131
|
name: string;
|
|
@@ -186,7 +205,7 @@ export declare class Variable extends Resource {
|
|
|
186
205
|
key: string;
|
|
187
206
|
value: string;
|
|
188
207
|
overriddenLocally?: boolean;
|
|
189
|
-
constructor(id: string | undefined, key: string, value: string, isSensitive: boolean);
|
|
208
|
+
constructor(id: string | undefined, key: string, value: string, isSensitive: boolean, resolvingTeamSlug?: string);
|
|
190
209
|
static validate(variable: Variable): Variable;
|
|
191
210
|
get contentHash(): string;
|
|
192
211
|
}
|
|
@@ -202,13 +221,50 @@ export interface ErrorHandlerConfig {
|
|
|
202
221
|
processSlug: string;
|
|
203
222
|
tag?: string | null;
|
|
204
223
|
}
|
|
224
|
+
export interface TeamVersionManifestEntry {
|
|
225
|
+
resourceType: 'MODULE' | 'PROCESS';
|
|
226
|
+
resourceId: string;
|
|
227
|
+
slug: string;
|
|
228
|
+
outcome: 'CREATED' | 'FAILED' | 'SKIPPED_EXISTS' | 'SKIPPED_NO_TAG' | 'UPDATED';
|
|
229
|
+
error?: string | null;
|
|
230
|
+
}
|
|
231
|
+
export interface TeamVersion {
|
|
232
|
+
id: string;
|
|
233
|
+
tag: string;
|
|
234
|
+
comment?: string | null;
|
|
235
|
+
manifest: TeamVersionManifestEntry[];
|
|
236
|
+
createdAt: string;
|
|
237
|
+
createdBy?: {
|
|
238
|
+
id: string;
|
|
239
|
+
name: string;
|
|
240
|
+
} | null;
|
|
241
|
+
}
|
|
242
|
+
export interface TeamVersionAlias {
|
|
243
|
+
id: string;
|
|
244
|
+
name: string;
|
|
245
|
+
tag: string;
|
|
246
|
+
createdAt: string;
|
|
247
|
+
updatedAt?: string | null;
|
|
248
|
+
}
|
|
249
|
+
export interface TeamVersionFileEntry {
|
|
250
|
+
tag: string;
|
|
251
|
+
comment?: string;
|
|
252
|
+
createdAt: string;
|
|
253
|
+
}
|
|
254
|
+
export interface TeamAliasFileEntry {
|
|
255
|
+
name: string;
|
|
256
|
+
tag: string;
|
|
257
|
+
}
|
|
205
258
|
export declare class Team extends Resource {
|
|
206
259
|
id: string | undefined;
|
|
207
260
|
slug: string;
|
|
208
261
|
zoneId?: string;
|
|
209
262
|
errorHandlerConfig?: ErrorHandlerConfig;
|
|
210
263
|
parentTeamSlugs: string[];
|
|
211
|
-
|
|
264
|
+
webhookAuth?: WebhookAuth;
|
|
265
|
+
versions?: TeamVersionFileEntry[];
|
|
266
|
+
aliases?: TeamAliasFileEntry[];
|
|
267
|
+
constructor(slug: string, parentTeamSlugs: string[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth);
|
|
212
268
|
get contentHash(): string;
|
|
213
269
|
}
|
|
214
270
|
export interface RawErrorHandlerConfig {
|
|
@@ -220,11 +276,16 @@ export interface RawTeamSettings {
|
|
|
220
276
|
zoneId?: string;
|
|
221
277
|
errorHandlerConfig?: RawErrorHandlerConfig | null;
|
|
222
278
|
parentTeamSlugs: string[];
|
|
279
|
+
webhookAuth?: WebhookAuth | null;
|
|
223
280
|
}
|
|
224
281
|
export interface TeamUpdateInput {
|
|
225
282
|
zoneId?: string;
|
|
226
283
|
parentTeamSlugs?: string[];
|
|
227
284
|
executionErrorHandlerConfig?: RawErrorHandlerConfig | null;
|
|
285
|
+
webhookAuth?: {
|
|
286
|
+
headerName?: string | null;
|
|
287
|
+
variableKey?: string | null;
|
|
288
|
+
} | null;
|
|
228
289
|
}
|
|
229
290
|
export type ResourceMetadata = {
|
|
230
291
|
[key: string]: any;
|
package/dist/core/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ServiceAccount = exports.Dependency = exports.DependenciesFlags = exports.TeamNameArg = exports.ResourceSlugArg = exports.ForceFlag = exports.ScriptLibraryNotFound = exports.ProcessNotFound = exports.Team = exports.ProgrammingLanguageManifest = exports.Variable = exports.VersionedScriptLibrary = exports.ScriptLibrary = exports.VersionedProcess = exports.Process = exports.programmingLanguageDisplayName = exports.ResourceStatus = exports.OnConflictResolution = exports.Resource = void 0;
|
|
3
|
+
exports.ServiceAccount = exports.Dependency = exports.DependenciesFlags = exports.TeamNameArg = exports.ResourceSlugArg = exports.ForceFlag = exports.ScriptLibraryNotFound = exports.ProcessNotFound = exports.Team = exports.ProgrammingLanguageManifest = exports.Variable = exports.VersionedScriptLibrary = exports.ScriptLibrary = exports.VersionedProcess = exports.Process = exports.DEFAULT_WEBHOOK_AUTH_HEADER = exports.programmingLanguageDisplayName = exports.ResourceStatus = exports.OnConflictResolution = exports.Resource = void 0;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
const crypto = require("node:crypto");
|
|
6
6
|
const canonical_hash_1 = require("../utils/canonical-hash");
|
|
@@ -37,6 +37,8 @@ const programmingLanguageDisplayName = (programmingLanguage) => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
exports.programmingLanguageDisplayName = programmingLanguageDisplayName;
|
|
40
|
+
// The header assumed when none is configured; its value carries a Bearer scheme.
|
|
41
|
+
exports.DEFAULT_WEBHOOK_AUTH_HEADER = 'Authorization';
|
|
40
42
|
class Process extends Resource {
|
|
41
43
|
constructor(id, slug, script, readme, manifest, settings, metadata, resolvingTeamSlug) {
|
|
42
44
|
super();
|
|
@@ -116,13 +118,14 @@ class VersionedScriptLibrary extends Resource {
|
|
|
116
118
|
}
|
|
117
119
|
exports.VersionedScriptLibrary = VersionedScriptLibrary;
|
|
118
120
|
class Variable extends Resource {
|
|
119
|
-
constructor(id, key, value, isSensitive) {
|
|
121
|
+
constructor(id, key, value, isSensitive, resolvingTeamSlug) {
|
|
120
122
|
super();
|
|
121
123
|
this.id = id;
|
|
122
124
|
this.slug = key;
|
|
123
125
|
this.key = key;
|
|
124
126
|
this.value = value;
|
|
125
127
|
this.isSensitive = isSensitive;
|
|
128
|
+
this.resolvingTeamSlug = resolvingTeamSlug;
|
|
126
129
|
}
|
|
127
130
|
static validate(variable) {
|
|
128
131
|
if (!Variable.ENV_KEY_REGEX.test(variable.key)) {
|
|
@@ -163,19 +166,27 @@ class ProgrammingLanguageManifest extends Resource {
|
|
|
163
166
|
}
|
|
164
167
|
exports.ProgrammingLanguageManifest = ProgrammingLanguageManifest;
|
|
165
168
|
class Team extends Resource {
|
|
166
|
-
constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig) {
|
|
169
|
+
constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig, webhookAuth) {
|
|
167
170
|
super();
|
|
168
171
|
this.slug = slug;
|
|
169
172
|
this.parentTeamSlugs = parentTeamSlugs;
|
|
170
173
|
this.zoneId = zoneId;
|
|
171
174
|
this.errorHandlerConfig = errorHandlerConfig;
|
|
175
|
+
this.webhookAuth = webhookAuth;
|
|
172
176
|
}
|
|
173
177
|
get contentHash() {
|
|
174
|
-
|
|
178
|
+
const payload = {
|
|
175
179
|
zoneId: this.zoneId || null,
|
|
176
180
|
errorHandlerConfig: this.errorHandlerConfig || null,
|
|
177
181
|
parentTeamSlugs: [...(this.parentTeamSlugs || [])].sort(),
|
|
178
|
-
}
|
|
182
|
+
};
|
|
183
|
+
// Inserted only when set, so every team.json that predates webhook auth keeps the
|
|
184
|
+
// hash already stored in .fcode and does not report a phantom conflict. A key present
|
|
185
|
+
// with an undefined value would not do: canonicalStringify writes that as null.
|
|
186
|
+
if (this.webhookAuth) {
|
|
187
|
+
payload.webhookAuth = this.webhookAuth;
|
|
188
|
+
}
|
|
189
|
+
return (0, canonical_hash_1.canonicalJsonHash)(payload);
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
exports.Team = Team;
|
package/dist/env.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare const envSchema: z.ZodObject<{
|
|
|
27
27
|
false: "false";
|
|
28
28
|
}>>;
|
|
29
29
|
CLI_UX_SKIP_TTY_CHECK: z.ZodOptional<z.ZodString>;
|
|
30
|
-
|
|
30
|
+
FC_COMMAND: z.ZodOptional<z.ZodString>;
|
|
31
31
|
}, z.core.$strip>;
|
|
32
32
|
export declare const env: {
|
|
33
33
|
FCODE_CLOUD_URL: string;
|
|
@@ -38,6 +38,6 @@ export declare const env: {
|
|
|
38
38
|
FCODE_DISABLE_UPDATE_NOTIFIER?: "true" | "false" | undefined;
|
|
39
39
|
SILENT_MODE?: "true" | "false" | undefined;
|
|
40
40
|
CLI_UX_SKIP_TTY_CHECK?: string | undefined;
|
|
41
|
-
|
|
41
|
+
FC_COMMAND?: string | undefined;
|
|
42
42
|
};
|
|
43
43
|
export default env;
|
package/dist/env.js
CHANGED
|
@@ -25,7 +25,7 @@ exports.envSchema = zod_1.z.object({
|
|
|
25
25
|
SILENT_MODE: zod_1.z.enum(['true', 'false']).optional(),
|
|
26
26
|
CLI_UX_SKIP_TTY_CHECK: zod_1.z.string().min(1).optional(),
|
|
27
27
|
// --- Debug tooling ---
|
|
28
|
-
|
|
28
|
+
FC_COMMAND: zod_1.z.string().min(1).optional(),
|
|
29
29
|
});
|
|
30
30
|
/** Treat empty strings (e.g. an unset shell/dotenv var rendered as "") as missing. */
|
|
31
31
|
const raw = Object.fromEntries(Object.entries(process.env).filter(([, value]) => value !== ''));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { RawTeamSettings, Team } from '../core/types';
|
|
1
|
+
import { RawTeamSettings, Team, TeamVersion, TeamVersionAlias } from '../core/types';
|
|
2
2
|
import WorkspaceConfig from '../config/workspace';
|
|
3
3
|
/**
|
|
4
|
-
* Reads/writes the singleton team.json (timezone, error handler, inheritance) at the
|
|
4
|
+
* Reads/writes the singleton team.json (timezone, error handler, inheritance, webhook auth) at the
|
|
5
5
|
* workspace root. Unlike collection resources this is not a ResourceRepositoryBase:
|
|
6
6
|
* there is exactly one team, so there is no slug-keyed collection to manage.
|
|
7
7
|
*
|
|
@@ -16,8 +16,16 @@ export default class TeamRepository {
|
|
|
16
16
|
save(team: Team): void;
|
|
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
|
+
/**
|
|
20
|
+
* Writes the cloud's team versions and aliases into the Team as the pull-only team.json
|
|
21
|
+
* fields (sorted for stable git diffs). These fields never enter the content hash and are
|
|
22
|
+
* stripped from pushes: all writes go through the team:versions/team:aliases commands.
|
|
23
|
+
*/
|
|
24
|
+
applyCloudVersioning(team: Team, versions: TeamVersion[], aliases: TeamVersionAlias[]): void;
|
|
19
25
|
resolveSlugToId(processSlug: string): Promise<string>;
|
|
20
26
|
resolveIdToSlug(processId: string): Promise<string>;
|
|
21
27
|
private parseErrorHandler;
|
|
28
|
+
/** A configuration without a variable key is meaningless, so it is read as absent. */
|
|
29
|
+
private parseWebhookAuth;
|
|
22
30
|
private toFile;
|
|
23
31
|
}
|
|
@@ -6,7 +6,7 @@ const workspace_1 = require("../config/workspace");
|
|
|
6
6
|
const file_utils_1 = require("../utils/file-utils");
|
|
7
7
|
const TEAM_FILENAME = 'team.json';
|
|
8
8
|
/**
|
|
9
|
-
* Reads/writes the singleton team.json (timezone, error handler, inheritance) at the
|
|
9
|
+
* Reads/writes the singleton team.json (timezone, error handler, inheritance, webhook auth) at the
|
|
10
10
|
* workspace root. Unlike collection resources this is not a ResourceRepositoryBase:
|
|
11
11
|
* there is exactly one team, so there is no slug-keyed collection to manage.
|
|
12
12
|
*
|
|
@@ -29,7 +29,15 @@ class TeamRepository {
|
|
|
29
29
|
}
|
|
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));
|
|
33
|
+
// Parsed so a find()+save() round-trip does not drop the pull-only fields.
|
|
34
|
+
if (Array.isArray(raw.versions)) {
|
|
35
|
+
team.versions = raw.versions;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(raw.aliases)) {
|
|
38
|
+
team.aliases = raw.aliases;
|
|
39
|
+
}
|
|
40
|
+
return team;
|
|
33
41
|
}
|
|
34
42
|
save(team) {
|
|
35
43
|
(0, file_utils_1.outputFileSyncIfChanged)(TEAM_FILENAME, JSON.stringify(this.toFile(team), null, 2));
|
|
@@ -43,7 +51,26 @@ class TeamRepository {
|
|
|
43
51
|
tag: raw.errorHandlerConfig.tag ?? null,
|
|
44
52
|
};
|
|
45
53
|
}
|
|
46
|
-
return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig);
|
|
54
|
+
return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig, this.parseWebhookAuth(raw.webhookAuth));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Writes the cloud's team versions and aliases into the Team as the pull-only team.json
|
|
58
|
+
* fields (sorted for stable git diffs). These fields never enter the content hash and are
|
|
59
|
+
* stripped from pushes: all writes go through the team:versions/team:aliases commands.
|
|
60
|
+
*/
|
|
61
|
+
applyCloudVersioning(team, versions, aliases) {
|
|
62
|
+
team.versions = versions
|
|
63
|
+
.map((version) => {
|
|
64
|
+
const entry = { tag: version.tag, createdAt: version.createdAt };
|
|
65
|
+
if (version.comment) {
|
|
66
|
+
entry.comment = version.comment;
|
|
67
|
+
}
|
|
68
|
+
return entry;
|
|
69
|
+
})
|
|
70
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
71
|
+
team.aliases = aliases
|
|
72
|
+
.map((alias) => ({ name: alias.name, tag: alias.tag }))
|
|
73
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
47
74
|
}
|
|
48
75
|
async resolveSlugToId(processSlug) {
|
|
49
76
|
const { processes } = await this.workspaceConfig.getActiveTeam();
|
|
@@ -60,6 +87,18 @@ class TeamRepository {
|
|
|
60
87
|
}
|
|
61
88
|
return { processSlug: raw.processSlug, tag: raw.tag ?? null };
|
|
62
89
|
}
|
|
90
|
+
/** A configuration without a variable key is meaningless, so it is read as absent. */
|
|
91
|
+
parseWebhookAuth(raw) {
|
|
92
|
+
if (!raw || typeof raw.variableKey !== 'string' || raw.variableKey.length === 0) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const webhookAuth = { variableKey: raw.variableKey };
|
|
96
|
+
// The default header is left out so a pull writes the minimal form.
|
|
97
|
+
if (typeof raw.headerName === 'string' && raw.headerName.length > 0 && raw.headerName !== types_1.DEFAULT_WEBHOOK_AUTH_HEADER) {
|
|
98
|
+
webhookAuth.headerName = raw.headerName;
|
|
99
|
+
}
|
|
100
|
+
return webhookAuth;
|
|
101
|
+
}
|
|
63
102
|
toFile(team) {
|
|
64
103
|
const file = { parentTeamSlugs: team.parentTeamSlugs ?? [] };
|
|
65
104
|
if (team.zoneId !== undefined) {
|
|
@@ -68,6 +107,17 @@ class TeamRepository {
|
|
|
68
107
|
if (team.errorHandlerConfig !== undefined) {
|
|
69
108
|
file.errorHandlerConfig = team.errorHandlerConfig;
|
|
70
109
|
}
|
|
110
|
+
// Last, so adding webhook auth to an existing team.json is a purely additive diff.
|
|
111
|
+
if (team.webhookAuth !== undefined) {
|
|
112
|
+
file.webhookAuth = team.webhookAuth;
|
|
113
|
+
}
|
|
114
|
+
// Pull-only global versioning fields, appended after the pushable settings.
|
|
115
|
+
if (team.versions !== undefined) {
|
|
116
|
+
file.versions = team.versions;
|
|
117
|
+
}
|
|
118
|
+
if (team.aliases !== undefined) {
|
|
119
|
+
file.aliases = team.aliases;
|
|
120
|
+
}
|
|
71
121
|
return file;
|
|
72
122
|
}
|
|
73
123
|
}
|
|
@@ -4,16 +4,40 @@ export declare const DEFAULT_SENSITIVE_VALUE = "********";
|
|
|
4
4
|
declare class VariableRepository extends ResourceRepositoryBase<Variable> {
|
|
5
5
|
private filename;
|
|
6
6
|
private localFilename;
|
|
7
|
+
private inheritedFilename;
|
|
7
8
|
private filenameInitPromise;
|
|
8
9
|
constructor();
|
|
9
10
|
private ensureFilenamesInitialized;
|
|
10
11
|
findAll(): Promise<Variable[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Values as a local run sees them. Precedence, lowest first: variables inherited from
|
|
14
|
+
* parent workspaces, this workspace's own variables, and the local overrides file — the
|
|
15
|
+
* same order the platform applies, where a workspace's own value wins over an inherited one.
|
|
16
|
+
* An empty value is not an override but an unset one: the platform rejects blank values, and
|
|
17
|
+
* a variable emptied in variables.env is a removal (see `mapResourceStatus`), so an empty
|
|
18
|
+
* entry falls through to the layer below rather than blanking it.
|
|
19
|
+
*/
|
|
11
20
|
findAllForExecution(): Promise<Variable[]>;
|
|
21
|
+
/**
|
|
22
|
+
* An inherited key the workspace has given a value of its own, overriding it. An empty entry
|
|
23
|
+
* does not count: the platform has no empty values, so it reads as "not set" here too.
|
|
24
|
+
*/
|
|
25
|
+
private overridesInherited;
|
|
12
26
|
find(slug: string): Promise<Variable>;
|
|
27
|
+
/**
|
|
28
|
+
* Keys declared in the workspace's own variables file that its metadata does not claim yet.
|
|
29
|
+
* A key the workspace inherits counts as one of those: the metadata still points at the
|
|
30
|
+
* parent's variable, and adding it is what turns the entry into this workspace's own.
|
|
31
|
+
*/
|
|
13
32
|
findSlugsNotAdded(): Promise<Array<string>>;
|
|
14
33
|
protected getResourcePath(_resource: Variable): string;
|
|
15
34
|
protected removeFiles(variable: Variable): Promise<void>;
|
|
16
35
|
protected resourceToMetadata(variable: Variable): ResourceMetadata;
|
|
36
|
+
/**
|
|
37
|
+
* A variable owned by this workspace goes to the committed variables file; one inherited
|
|
38
|
+
* from a parent workspace goes to the gitignored inherited file, so a local run can use it
|
|
39
|
+
* without the child workspace's git history carrying a copy of its parent's values.
|
|
40
|
+
*/
|
|
17
41
|
protected saveFiles(variable: Variable): Promise<void>;
|
|
18
42
|
metadataFromFiles(slug: string, opts: AddResourceOptions): Promise<ResourceMetadata>;
|
|
19
43
|
private readVariablesMeta;
|
|
@@ -20,6 +20,7 @@ class VariableRepository extends resource_1.default {
|
|
|
20
20
|
});
|
|
21
21
|
this.filename = null;
|
|
22
22
|
this.localFilename = null;
|
|
23
|
+
this.inheritedFilename = null;
|
|
23
24
|
this.filenameInitPromise = null;
|
|
24
25
|
}
|
|
25
26
|
async ensureFilenamesInitialized() {
|
|
@@ -30,40 +31,67 @@ class VariableRepository extends resource_1.default {
|
|
|
30
31
|
const globalConfig = new global_1.default();
|
|
31
32
|
this.filename = await globalConfig.getSetting(settings_1.SettingKeys.VARIABLES_FILE_NAME);
|
|
32
33
|
this.localFilename = await globalConfig.getSetting(settings_1.SettingKeys.LOCAL_VARIABLES_FILE_NAME);
|
|
34
|
+
this.inheritedFilename = await globalConfig.getSetting(settings_1.SettingKeys.INHERITED_VARIABLES_FILE_NAME);
|
|
33
35
|
})();
|
|
34
36
|
return this.filenameInitPromise;
|
|
35
37
|
}
|
|
36
38
|
async findAll() {
|
|
37
39
|
await this.ensureFilenamesInitialized();
|
|
38
|
-
const rawVariables = this.readVariablesFromFile();
|
|
39
|
-
const
|
|
40
|
+
const rawVariables = this.readVariablesFromFile('own');
|
|
41
|
+
const inheritedRawVariables = this.readVariablesFromFile('inherited');
|
|
42
|
+
const localOverrideKeys = new Set(Object.keys(this.readVariablesFromFile('local')));
|
|
40
43
|
const meta = this.readVariablesMeta();
|
|
41
44
|
const variables = [];
|
|
42
45
|
const { variables: localVariables } = await this.workspaceConfig.getActiveTeam();
|
|
43
46
|
for (const localVariableKey of Object.keys(localVariables)) {
|
|
44
47
|
const localVariable = localVariables[localVariableKey];
|
|
45
48
|
const isSensitive = meta[localVariable.key]?.isSensitive ?? false;
|
|
46
|
-
|
|
49
|
+
// Writing an inherited key into variables.env is how you override it, so from that
|
|
50
|
+
// moment the variable is this workspace's: it drops the parent's id and inherited
|
|
51
|
+
// marker, which is what makes `push` create it here instead of skipping it.
|
|
52
|
+
const overridesInherited = this.overridesInherited(localVariable, rawVariables);
|
|
53
|
+
const resolvingTeamSlug = overridesInherited ? undefined : localVariable.resolvingTeamSlug;
|
|
54
|
+
const id = overridesInherited ? undefined : localVariable.id;
|
|
55
|
+
const rawValue = resolvingTeamSlug ? inheritedRawVariables[localVariable.key] : rawVariables[localVariable.key];
|
|
56
|
+
const variable = types_1.Variable.validate(new types_1.Variable(id, localVariable.key, rawValue, isSensitive, resolvingTeamSlug));
|
|
47
57
|
variable.overriddenLocally = localOverrideKeys.has(localVariable.key);
|
|
48
58
|
variables.push(variable);
|
|
49
59
|
}
|
|
50
60
|
return variables;
|
|
51
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Values as a local run sees them. Precedence, lowest first: variables inherited from
|
|
64
|
+
* parent workspaces, this workspace's own variables, and the local overrides file — the
|
|
65
|
+
* same order the platform applies, where a workspace's own value wins over an inherited one.
|
|
66
|
+
* An empty value is not an override but an unset one: the platform rejects blank values, and
|
|
67
|
+
* a variable emptied in variables.env is a removal (see `mapResourceStatus`), so an empty
|
|
68
|
+
* entry falls through to the layer below rather than blanking it.
|
|
69
|
+
*/
|
|
52
70
|
async findAllForExecution() {
|
|
53
71
|
await this.ensureFilenamesInitialized();
|
|
54
|
-
const rawVariables = this.readVariablesFromFile();
|
|
55
|
-
const
|
|
72
|
+
const rawVariables = this.readVariablesFromFile('own');
|
|
73
|
+
const inheritedRawVariables = this.readVariablesFromFile('inherited');
|
|
74
|
+
const localRawVariables = this.readVariablesFromFile('local');
|
|
56
75
|
const meta = this.readVariablesMeta();
|
|
57
76
|
const variables = [];
|
|
58
77
|
const { variables: localVariables } = await this.workspaceConfig.getActiveTeam();
|
|
59
78
|
for (const localVariableKey of Object.keys(localVariables)) {
|
|
60
79
|
const localVariable = localVariables[localVariableKey];
|
|
61
|
-
const
|
|
62
|
-
const isSensitive = meta[
|
|
63
|
-
|
|
80
|
+
const key = localVariable.key;
|
|
81
|
+
const isSensitive = meta[key]?.isSensitive ?? false;
|
|
82
|
+
const value = localRawVariables[key] || rawVariables[key] || inheritedRawVariables[key];
|
|
83
|
+
const resolvingTeamSlug = this.overridesInherited(localVariable, rawVariables) ? undefined : localVariable.resolvingTeamSlug;
|
|
84
|
+
variables.push(types_1.Variable.validate(new types_1.Variable(localVariable.id, key, value, isSensitive, resolvingTeamSlug)));
|
|
64
85
|
}
|
|
65
86
|
return variables;
|
|
66
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* An inherited key the workspace has given a value of its own, overriding it. An empty entry
|
|
90
|
+
* does not count: the platform has no empty values, so it reads as "not set" here too.
|
|
91
|
+
*/
|
|
92
|
+
overridesInherited(metadata, ownRawVariables) {
|
|
93
|
+
return Boolean(metadata.resolvingTeamSlug) && Boolean(ownRawVariables[metadata.key]);
|
|
94
|
+
}
|
|
67
95
|
async find(slug) {
|
|
68
96
|
let variables = await this.findAll();
|
|
69
97
|
variables = variables.filter((v) => v.key === slug);
|
|
@@ -72,12 +100,19 @@ class VariableRepository extends resource_1.default {
|
|
|
72
100
|
}
|
|
73
101
|
throw new errors_1.LocalVariableNotFound(new Error('Variable not found on team metadata'), slug);
|
|
74
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Keys declared in the workspace's own variables file that its metadata does not claim yet.
|
|
105
|
+
* A key the workspace inherits counts as one of those: the metadata still points at the
|
|
106
|
+
* parent's variable, and adding it is what turns the entry into this workspace's own.
|
|
107
|
+
*/
|
|
75
108
|
async findSlugsNotAdded() {
|
|
76
109
|
await this.ensureFilenamesInitialized();
|
|
110
|
+
const ownRawVariables = this.readVariablesFromFile('own');
|
|
77
111
|
const { variables: localVariables } = await this.workspaceConfig.getActiveTeam();
|
|
78
112
|
const slugsNotAdded = [];
|
|
79
|
-
for (const
|
|
80
|
-
|
|
113
|
+
for (const key of Object.keys(ownRawVariables)) {
|
|
114
|
+
const metadata = localVariables[key];
|
|
115
|
+
if (!metadata || this.overridesInherited(metadata, ownRawVariables)) {
|
|
81
116
|
slugsNotAdded.push(key);
|
|
82
117
|
}
|
|
83
118
|
}
|
|
@@ -88,9 +123,10 @@ class VariableRepository extends resource_1.default {
|
|
|
88
123
|
}
|
|
89
124
|
async removeFiles(variable) {
|
|
90
125
|
await this.ensureFilenamesInitialized();
|
|
91
|
-
|
|
126
|
+
const file = variable.resolvingTeamSlug ? 'inherited' : 'own';
|
|
127
|
+
const rawVariables = this.readVariablesFromFile(file);
|
|
92
128
|
delete rawVariables[variable.key];
|
|
93
|
-
this.writeRawVariables(rawVariables);
|
|
129
|
+
this.writeRawVariables(rawVariables, file);
|
|
94
130
|
const meta = this.readVariablesMeta();
|
|
95
131
|
delete meta[variable.key];
|
|
96
132
|
this.writeVariablesMeta(meta);
|
|
@@ -100,13 +136,20 @@ class VariableRepository extends resource_1.default {
|
|
|
100
136
|
id: variable.id,
|
|
101
137
|
slug: variable.slug,
|
|
102
138
|
key: variable.key,
|
|
139
|
+
...(variable.resolvingTeamSlug && { resolvingTeamSlug: variable.resolvingTeamSlug }),
|
|
103
140
|
};
|
|
104
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* A variable owned by this workspace goes to the committed variables file; one inherited
|
|
144
|
+
* from a parent workspace goes to the gitignored inherited file, so a local run can use it
|
|
145
|
+
* without the child workspace's git history carrying a copy of its parent's values.
|
|
146
|
+
*/
|
|
105
147
|
async saveFiles(variable) {
|
|
106
148
|
await this.ensureFilenamesInitialized();
|
|
107
|
-
|
|
108
|
-
rawVariables =
|
|
109
|
-
this.
|
|
149
|
+
const file = variable.resolvingTeamSlug ? 'inherited' : 'own';
|
|
150
|
+
const rawVariables = this.readVariablesFromFile(file);
|
|
151
|
+
rawVariables[variable.key] = this.valueToSave(variable);
|
|
152
|
+
this.writeRawVariables(rawVariables, file);
|
|
110
153
|
const meta = this.readVariablesMeta();
|
|
111
154
|
meta[variable.key] = { isSensitive: variable.isSensitive };
|
|
112
155
|
this.writeVariablesMeta(meta);
|
|
@@ -139,15 +182,20 @@ class VariableRepository extends resource_1.default {
|
|
|
139
182
|
}, {});
|
|
140
183
|
(0, file_utils_1.outputFileSyncIfChanged)(VARIABLES_META_FILENAME, JSON.stringify(sorted, null, 2));
|
|
141
184
|
}
|
|
142
|
-
filePath(
|
|
143
|
-
if (!this.filename || !this.localFilename) {
|
|
185
|
+
filePath(file = 'own') {
|
|
186
|
+
if (!this.filename || !this.localFilename || !this.inheritedFilename) {
|
|
144
187
|
throw new Error('Filenames not initialized. Call ensureFilenamesInitialized() first.');
|
|
145
188
|
}
|
|
146
|
-
|
|
189
|
+
const filenames = {
|
|
190
|
+
own: this.filename,
|
|
191
|
+
local: this.localFilename,
|
|
192
|
+
inherited: this.inheritedFilename,
|
|
193
|
+
};
|
|
194
|
+
return path.join(filenames[file]);
|
|
147
195
|
}
|
|
148
|
-
readVariablesFromFile(
|
|
196
|
+
readVariablesFromFile(file = 'own') {
|
|
149
197
|
try {
|
|
150
|
-
const result = (0, dotenv_1.parse)(fs.readFileSync(this.filePath(
|
|
198
|
+
const result = (0, dotenv_1.parse)(fs.readFileSync(this.filePath(file), 'utf8')) || {};
|
|
151
199
|
for (const [key, value] of Object.entries(result)) {
|
|
152
200
|
result[key] = (0, env_utils_1.unescapeEnvValue)(value);
|
|
153
201
|
}
|
|
@@ -163,12 +211,12 @@ class VariableRepository extends resource_1.default {
|
|
|
163
211
|
}
|
|
164
212
|
return variable.value;
|
|
165
213
|
}
|
|
166
|
-
writeRawVariables(rawVariables) {
|
|
214
|
+
writeRawVariables(rawVariables, file = 'own') {
|
|
167
215
|
const envContent = Object.entries(rawVariables)
|
|
168
216
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
169
217
|
.map(([key, value]) => `${key}=${(0, env_utils_1.escapeEnvValue)(value)}`)
|
|
170
218
|
.join('\n');
|
|
171
|
-
(0, file_utils_1.outputFileSyncIfChanged)(this.filePath(), envContent);
|
|
219
|
+
(0, file_utils_1.outputFileSyncIfChanged)(this.filePath(file), envContent);
|
|
172
220
|
}
|
|
173
221
|
}
|
|
174
222
|
exports.default = VariableRepository;
|
package/dist/runtime/executor.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('node:path')
|
|
|
3
3
|
const { execSync } = require('node:child_process')
|
|
4
4
|
const os = require('node:os')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const FC_COMMAND = process.env.FC_COMMAND ?? "fcode"
|
|
7
7
|
|
|
8
8
|
const file = process.argv[2]
|
|
9
9
|
if (!fs.existsSync(file)) {
|
|
@@ -36,7 +36,7 @@ let dependenciesPath = ''
|
|
|
36
36
|
try {
|
|
37
37
|
version = getVersion(relativePath)
|
|
38
38
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fcode-'))
|
|
39
|
-
const command = [
|
|
39
|
+
const command = [FC_COMMAND, 'pre-debug', absolutePath, '-t', tempDir]
|
|
40
40
|
if (version) {
|
|
41
41
|
command.push('-v')
|
|
42
42
|
command.push(version)
|
package/dist/runtime/executor.py
CHANGED
|
@@ -4,7 +4,7 @@ import subprocess
|
|
|
4
4
|
import tempfile
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
FC_COMMAND = os.environ.get("FC_COMMAND", "fcode")
|
|
8
8
|
file = sys.argv[1]
|
|
9
9
|
if not os.path.exists(file):
|
|
10
10
|
print("File not found: " + file)
|
|
@@ -31,7 +31,7 @@ main_file_path = ""
|
|
|
31
31
|
try:
|
|
32
32
|
version = get_version(file)
|
|
33
33
|
tempDir = tempfile.TemporaryDirectory()
|
|
34
|
-
command = [
|
|
34
|
+
command = [FC_COMMAND, "pre-debug", file, "-t", tempDir.name]
|
|
35
35
|
if version != None:
|
|
36
36
|
command.append("-v")
|
|
37
37
|
command.append(version)
|
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
FC_COMMAND = os.environ.get("FC_COMMAND", "fcode")
|
|
7
7
|
|
|
8
8
|
metadata = {
|
|
9
9
|
"execution": {"process": {"name": "", "id": ""}},
|
|
@@ -24,7 +24,7 @@ else:
|
|
|
24
24
|
os.environ.get("FCODE_LOG_LEVEL", "") == "DEBUG" and print(
|
|
25
25
|
"[FCODE LIBRARY] Reading metadata from process-metadata command"
|
|
26
26
|
)
|
|
27
|
-
command = [
|
|
27
|
+
command = [FC_COMMAND, "process-metadata"]
|
|
28
28
|
if process_slug:
|
|
29
29
|
command.append(process_slug)
|
|
30
30
|
if process_slug and process_version:
|
|
@@ -6,6 +6,9 @@ declare const AuthService: {
|
|
|
6
6
|
getUserInfo(authUrl: string, token: Token): Promise<UserInfo>;
|
|
7
7
|
globalConfig: GlobalConfig;
|
|
8
8
|
isAuthenticated(url: string): Promise<boolean>;
|
|
9
|
+
isTokenExpiring(token: Token): boolean;
|
|
10
|
+
refreshToken(authUrl: string, refreshToken: string): Promise<Token>;
|
|
11
|
+
tryRefresh(url: string, { authUrl, token, userInfo }: AuthInfo): Promise<boolean>;
|
|
9
12
|
login({ url, authUrl, email, password, clientId, clientSecret, accessToken }: Credentials): Promise<AuthInfo>;
|
|
10
13
|
logout(url: string): Promise<void>;
|
|
11
14
|
};
|