@factorialco/fcode-cli 2.0.0 → 2.2.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 +248 -2
- package/dist/commands/add.js +3 -1
- package/dist/commands/clone.d.ts +1 -0
- package/dist/commands/clone.js +5 -0
- package/dist/commands/i18n/add.d.ts +9 -0
- package/dist/commands/i18n/add.js +13 -0
- package/dist/commands/i18n/pull.d.ts +12 -0
- package/dist/commands/i18n/pull.js +14 -0
- package/dist/commands/i18n/push.d.ts +12 -0
- package/dist/commands/i18n/push.js +14 -0
- package/dist/commands/i18n/remove.d.ts +9 -0
- package/dist/commands/i18n/remove.js +19 -0
- package/dist/commands/i18n/reset.d.ts +9 -0
- package/dist/commands/i18n/reset.js +13 -0
- package/dist/commands/i18n/status.d.ts +11 -0
- package/dist/commands/i18n/status.js +42 -0
- package/dist/commands/pull.js +5 -3
- package/dist/commands/push.js +5 -3
- package/dist/commands/reset.js +3 -1
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +5 -2
- package/dist/commands/setup-debug.js +2 -2
- package/dist/commands/status.js +4 -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 +6 -0
- 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/remote.js +1 -1
- package/dist/config/settings.d.ts +1 -0
- package/dist/config/settings.js +6 -0
- package/dist/config/workspace.js +10 -0
- package/dist/core/commands/i18n-command.d.ts +5 -0
- package/dist/core/commands/i18n-command.js +16 -0
- package/dist/core/errors.d.ts +6 -0
- package/dist/core/errors.js +19 -1
- package/dist/core/executors/execution-task.js +4 -0
- package/dist/core/executors/javascript-execution-task.js +26 -1
- package/dist/core/resource-pusher.js +15 -0
- package/dist/core/types.d.ts +66 -2
- package/dist/core/types.js +45 -3
- package/dist/env.d.ts +2 -2
- package/dist/env.js +1 -1
- package/dist/repositories/locale.d.ts +24 -0
- package/dist/repositories/locale.js +102 -0
- package/dist/repositories/resource.js +4 -1
- package/dist/repositories/team.d.ts +7 -1
- package/dist/repositories/team.js +39 -2
- 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 +3 -2
- package/dist/runtime/libs/python/fcode/context.py +43 -1
- package/dist/services/fcode-graphql-client.d.ts +20 -1
- package/dist/services/fcode-graphql-client.js +276 -17
- package/dist/services/locale-merger.d.ts +23 -0
- package/dist/services/locale-merger.js +111 -0
- package/dist/services/locale-resolver.d.ts +18 -0
- package/dist/services/locale-resolver.js +120 -0
- package/dist/services/process-runner.d.ts +1 -0
- package/dist/services/process-runner.js +2 -2
- package/dist/utils/gitignore.d.ts +2 -0
- package/dist/utils/gitignore.js +9 -0
- package/oclif.manifest.json +511 -127
- package/package.json +11 -2
package/dist/core/types.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface TeamMetadata {
|
|
|
41
41
|
variables: {
|
|
42
42
|
[key: string]: Variable;
|
|
43
43
|
};
|
|
44
|
+
i18n: {
|
|
45
|
+
[key: string]: Locale;
|
|
46
|
+
};
|
|
44
47
|
dependencies: {
|
|
45
48
|
[key: string]: ProgrammingLanguageManifest;
|
|
46
49
|
};
|
|
@@ -187,6 +190,27 @@ export declare class ScriptLibrary extends Resource {
|
|
|
187
190
|
constructor(id: string, name: string, sourceCode: string, programmingLanguage: ProgrammingLanguage, resolvingTeamSlug?: string);
|
|
188
191
|
get contentHash(): string;
|
|
189
192
|
}
|
|
193
|
+
export declare class Locale extends Resource {
|
|
194
|
+
static LOCALE_REGEX: RegExp;
|
|
195
|
+
static INHERITED_SUFFIX: string;
|
|
196
|
+
id: string | undefined;
|
|
197
|
+
slug: string;
|
|
198
|
+
locale: string;
|
|
199
|
+
content: string;
|
|
200
|
+
constructor(id: string | undefined, locale: string, content: string, resolvingTeamSlug?: string);
|
|
201
|
+
/**
|
|
202
|
+
* The name a locale is tracked and stored under. Unlike every other resource, the identifier is
|
|
203
|
+
* not unique: a workspace sees its own `en` and each parent workspace's `en`, and all of them are
|
|
204
|
+
* live, because an override is layered over the parent's file key by key rather than replacing
|
|
205
|
+
* it. Naming files after the identifier alone would keep only one of them.
|
|
206
|
+
*
|
|
207
|
+
* A workspace directory therefore holds two files at most per identifier: `en.yaml` for its own
|
|
208
|
+
* and `en.inherited.yaml` for everything it inherits, which `mergeInheritedLocales` collapses
|
|
209
|
+
* into one file. An identifier cannot contain a dot, so the name parses back.
|
|
210
|
+
*/
|
|
211
|
+
static slugFor(locale: string, resolvingTeamSlug?: string): string;
|
|
212
|
+
get contentHash(): string;
|
|
213
|
+
}
|
|
190
214
|
export declare class VersionedScriptLibrary extends Resource {
|
|
191
215
|
id: string;
|
|
192
216
|
slug: string;
|
|
@@ -205,7 +229,7 @@ export declare class Variable extends Resource {
|
|
|
205
229
|
key: string;
|
|
206
230
|
value: string;
|
|
207
231
|
overriddenLocally?: boolean;
|
|
208
|
-
constructor(id: string | undefined, key: string, value: string, isSensitive: boolean);
|
|
232
|
+
constructor(id: string | undefined, key: string, value: string, isSensitive: boolean, resolvingTeamSlug?: string);
|
|
209
233
|
static validate(variable: Variable): Variable;
|
|
210
234
|
get contentHash(): string;
|
|
211
235
|
}
|
|
@@ -221,6 +245,40 @@ export interface ErrorHandlerConfig {
|
|
|
221
245
|
processSlug: string;
|
|
222
246
|
tag?: string | null;
|
|
223
247
|
}
|
|
248
|
+
export interface TeamVersionManifestEntry {
|
|
249
|
+
resourceType: 'MODULE' | 'PROCESS';
|
|
250
|
+
resourceId: string;
|
|
251
|
+
slug: string;
|
|
252
|
+
outcome: 'CREATED' | 'FAILED' | 'SKIPPED_EXISTS' | 'SKIPPED_NO_TAG' | 'UPDATED';
|
|
253
|
+
error?: string | null;
|
|
254
|
+
}
|
|
255
|
+
export interface TeamVersion {
|
|
256
|
+
id: string;
|
|
257
|
+
tag: string;
|
|
258
|
+
comment?: string | null;
|
|
259
|
+
manifest: TeamVersionManifestEntry[];
|
|
260
|
+
createdAt: string;
|
|
261
|
+
createdBy?: {
|
|
262
|
+
id: string;
|
|
263
|
+
name: string;
|
|
264
|
+
} | null;
|
|
265
|
+
}
|
|
266
|
+
export interface TeamVersionAlias {
|
|
267
|
+
id: string;
|
|
268
|
+
name: string;
|
|
269
|
+
tag: string;
|
|
270
|
+
createdAt: string;
|
|
271
|
+
updatedAt?: string | null;
|
|
272
|
+
}
|
|
273
|
+
export interface TeamVersionFileEntry {
|
|
274
|
+
tag: string;
|
|
275
|
+
comment?: string;
|
|
276
|
+
createdAt: string;
|
|
277
|
+
}
|
|
278
|
+
export interface TeamAliasFileEntry {
|
|
279
|
+
name: string;
|
|
280
|
+
tag: string;
|
|
281
|
+
}
|
|
224
282
|
export declare class Team extends Resource {
|
|
225
283
|
id: string | undefined;
|
|
226
284
|
slug: string;
|
|
@@ -228,7 +286,10 @@ export declare class Team extends Resource {
|
|
|
228
286
|
errorHandlerConfig?: ErrorHandlerConfig;
|
|
229
287
|
parentTeamSlugs: string[];
|
|
230
288
|
webhookAuth?: WebhookAuth;
|
|
231
|
-
|
|
289
|
+
primaryLocale?: string;
|
|
290
|
+
versions?: TeamVersionFileEntry[];
|
|
291
|
+
aliases?: TeamAliasFileEntry[];
|
|
292
|
+
constructor(slug: string, parentTeamSlugs: string[], zoneId?: string, errorHandlerConfig?: ErrorHandlerConfig, webhookAuth?: WebhookAuth, primaryLocale?: string);
|
|
232
293
|
get contentHash(): string;
|
|
233
294
|
}
|
|
234
295
|
export interface RawErrorHandlerConfig {
|
|
@@ -241,6 +302,7 @@ export interface RawTeamSettings {
|
|
|
241
302
|
errorHandlerConfig?: RawErrorHandlerConfig | null;
|
|
242
303
|
parentTeamSlugs: string[];
|
|
243
304
|
webhookAuth?: WebhookAuth | null;
|
|
305
|
+
primaryLocale?: string | null;
|
|
244
306
|
}
|
|
245
307
|
export interface TeamUpdateInput {
|
|
246
308
|
zoneId?: string;
|
|
@@ -250,6 +312,7 @@ export interface TeamUpdateInput {
|
|
|
250
312
|
headerName?: string | null;
|
|
251
313
|
variableKey?: string | null;
|
|
252
314
|
} | null;
|
|
315
|
+
primaryLocale?: string;
|
|
253
316
|
}
|
|
254
317
|
export type ResourceMetadata = {
|
|
255
318
|
[key: string]: any;
|
|
@@ -281,6 +344,7 @@ export type ExecutionOptions = {
|
|
|
281
344
|
hookFilePath?: string;
|
|
282
345
|
hookFunction?: string;
|
|
283
346
|
silent?: boolean;
|
|
347
|
+
locale?: string;
|
|
284
348
|
};
|
|
285
349
|
export declare const ForceFlag: {
|
|
286
350
|
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
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.DEFAULT_WEBHOOK_AUTH_HEADER = 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.Locale = 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");
|
|
@@ -103,6 +103,41 @@ class ScriptLibrary extends Resource {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
exports.ScriptLibrary = ScriptLibrary;
|
|
106
|
+
class Locale extends Resource {
|
|
107
|
+
constructor(id, locale, content, resolvingTeamSlug) {
|
|
108
|
+
super();
|
|
109
|
+
this.id = id;
|
|
110
|
+
this.slug = Locale.slugFor(locale, resolvingTeamSlug);
|
|
111
|
+
this.locale = locale;
|
|
112
|
+
this.content = content;
|
|
113
|
+
this.resolvingTeamSlug = resolvingTeamSlug;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The name a locale is tracked and stored under. Unlike every other resource, the identifier is
|
|
117
|
+
* not unique: a workspace sees its own `en` and each parent workspace's `en`, and all of them are
|
|
118
|
+
* live, because an override is layered over the parent's file key by key rather than replacing
|
|
119
|
+
* it. Naming files after the identifier alone would keep only one of them.
|
|
120
|
+
*
|
|
121
|
+
* A workspace directory therefore holds two files at most per identifier: `en.yaml` for its own
|
|
122
|
+
* and `en.inherited.yaml` for everything it inherits, which `mergeInheritedLocales` collapses
|
|
123
|
+
* into one file. An identifier cannot contain a dot, so the name parses back.
|
|
124
|
+
*/
|
|
125
|
+
static slugFor(locale, resolvingTeamSlug) {
|
|
126
|
+
return resolvingTeamSlug ? `${locale}${Locale.INHERITED_SUFFIX}` : locale;
|
|
127
|
+
}
|
|
128
|
+
// Hashed over the raw YAML: the file is the source of truth and round-trips comments and
|
|
129
|
+
// ordering, so any edit — even one that parses to the same translations — is a change to push.
|
|
130
|
+
get contentHash() {
|
|
131
|
+
return crypto.createHash('md5').update(this.content).digest('hex');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.Locale = Locale;
|
|
135
|
+
// Free-form identifier rather than a validated BCP-47 tag, so a workspace can use whatever it
|
|
136
|
+
// already names its translations after. Case is significant: pt-BR and pt-br are different
|
|
137
|
+
// locales, and the platform's unique index is case-sensitive too.
|
|
138
|
+
Locale.LOCALE_REGEX = /^[A-Za-z0-9][\w-]{0,19}$/;
|
|
139
|
+
// Marks a file as owned by a parent workspace, the way `variables.inherited.env` does.
|
|
140
|
+
Locale.INHERITED_SUFFIX = '.inherited';
|
|
106
141
|
class VersionedScriptLibrary extends Resource {
|
|
107
142
|
constructor(id, tag, sourceCode, programmingLanguage) {
|
|
108
143
|
super();
|
|
@@ -118,13 +153,14 @@ class VersionedScriptLibrary extends Resource {
|
|
|
118
153
|
}
|
|
119
154
|
exports.VersionedScriptLibrary = VersionedScriptLibrary;
|
|
120
155
|
class Variable extends Resource {
|
|
121
|
-
constructor(id, key, value, isSensitive) {
|
|
156
|
+
constructor(id, key, value, isSensitive, resolvingTeamSlug) {
|
|
122
157
|
super();
|
|
123
158
|
this.id = id;
|
|
124
159
|
this.slug = key;
|
|
125
160
|
this.key = key;
|
|
126
161
|
this.value = value;
|
|
127
162
|
this.isSensitive = isSensitive;
|
|
163
|
+
this.resolvingTeamSlug = resolvingTeamSlug;
|
|
128
164
|
}
|
|
129
165
|
static validate(variable) {
|
|
130
166
|
if (!Variable.ENV_KEY_REGEX.test(variable.key)) {
|
|
@@ -165,13 +201,14 @@ class ProgrammingLanguageManifest extends Resource {
|
|
|
165
201
|
}
|
|
166
202
|
exports.ProgrammingLanguageManifest = ProgrammingLanguageManifest;
|
|
167
203
|
class Team extends Resource {
|
|
168
|
-
constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig, webhookAuth) {
|
|
204
|
+
constructor(slug, parentTeamSlugs, zoneId, errorHandlerConfig, webhookAuth, primaryLocale) {
|
|
169
205
|
super();
|
|
170
206
|
this.slug = slug;
|
|
171
207
|
this.parentTeamSlugs = parentTeamSlugs;
|
|
172
208
|
this.zoneId = zoneId;
|
|
173
209
|
this.errorHandlerConfig = errorHandlerConfig;
|
|
174
210
|
this.webhookAuth = webhookAuth;
|
|
211
|
+
this.primaryLocale = primaryLocale;
|
|
175
212
|
}
|
|
176
213
|
get contentHash() {
|
|
177
214
|
const payload = {
|
|
@@ -185,6 +222,11 @@ class Team extends Resource {
|
|
|
185
222
|
if (this.webhookAuth) {
|
|
186
223
|
payload.webhookAuth = this.webhookAuth;
|
|
187
224
|
}
|
|
225
|
+
// Same insert-only-when-set rule, for the same reason: a team.json written before locales
|
|
226
|
+
// existed must keep the hash already stored in .fcode.
|
|
227
|
+
if (this.primaryLocale) {
|
|
228
|
+
payload.primaryLocale = this.primaryLocale;
|
|
229
|
+
}
|
|
188
230
|
return (0, canonical_hash_1.canonicalJsonHash)(payload);
|
|
189
231
|
}
|
|
190
232
|
}
|
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 !== ''));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Locale, ResourceMetadata } from '../core/types';
|
|
2
|
+
import ResourceRepositoryBase from './resource';
|
|
3
|
+
/**
|
|
4
|
+
* Locales live as one YAML file per locale directly under `i18n/`, named after the slug
|
|
5
|
+
* (`i18n/pt-BR.yaml`, `i18n/pt-BR.inherited.yaml`). A file, not a directory: a locale has exactly
|
|
6
|
+
* one artifact, and naming the file after the identifier is what lets `push` work without knowing
|
|
7
|
+
* any ids.
|
|
8
|
+
*
|
|
9
|
+
* A workspace keeps what it inherits next to what it owns, because overriding a locale only layers
|
|
10
|
+
* keys over the inherited file rather than replacing it — see {@link Locale.slugFor}.
|
|
11
|
+
*/
|
|
12
|
+
declare class LocaleRepository extends ResourceRepositoryBase<Locale> {
|
|
13
|
+
constructor();
|
|
14
|
+
find(slug: string): Promise<Locale>;
|
|
15
|
+
findSlugsNotAdded(): Promise<Array<string>>;
|
|
16
|
+
protected getResourcePath(locale: Locale): string;
|
|
17
|
+
protected removeFiles(locale: Locale): Promise<void>;
|
|
18
|
+
protected resourceToMetadata(locale: Locale): ResourceMetadata;
|
|
19
|
+
protected saveFiles(locale: Locale): Promise<void>;
|
|
20
|
+
metadataFromFiles(slug: string): Promise<ResourceMetadata>;
|
|
21
|
+
validSlug(slug: string): boolean;
|
|
22
|
+
slugFormatMessage(): string;
|
|
23
|
+
}
|
|
24
|
+
export default LocaleRepository;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const color_1 = require("@oclif/color");
|
|
6
|
+
const cli_1 = require("../utils/cli");
|
|
7
|
+
const errors_1 = require("../core/errors");
|
|
8
|
+
const types_1 = require("../core/types");
|
|
9
|
+
const file_utils_1 = require("../utils/file-utils");
|
|
10
|
+
const resource_1 = require("./resource");
|
|
11
|
+
const YAML_EXTENSION = '.yaml';
|
|
12
|
+
/**
|
|
13
|
+
* Locales live as one YAML file per locale directly under `i18n/`, named after the slug
|
|
14
|
+
* (`i18n/pt-BR.yaml`, `i18n/pt-BR.inherited.yaml`). A file, not a directory: a locale has exactly
|
|
15
|
+
* one artifact, and naming the file after the identifier is what lets `push` work without knowing
|
|
16
|
+
* any ids.
|
|
17
|
+
*
|
|
18
|
+
* A workspace keeps what it inherits next to what it owns, because overriding a locale only layers
|
|
19
|
+
* keys over the inherited file rather than replacing it — see {@link Locale.slugFor}.
|
|
20
|
+
*/
|
|
21
|
+
class LocaleRepository extends resource_1.default {
|
|
22
|
+
constructor() {
|
|
23
|
+
super({
|
|
24
|
+
name: 'i18n',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async find(slug) {
|
|
28
|
+
const { [this._repositoryName]: locales } = await this.workspaceConfig.getActiveTeam();
|
|
29
|
+
const localeMetadata = locales?.[slug];
|
|
30
|
+
if (!localeMetadata) {
|
|
31
|
+
throw new errors_1.LocalLocaleNotFound(new Error('Locale not found on team metadata'), slug);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(this.getResourcePath(localeMetadata)).toString();
|
|
35
|
+
return new types_1.Locale(localeMetadata.id, localeMetadata.locale ?? slug, content, localeMetadata.resolvingTeamSlug);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new errors_1.LocalLocaleNotFound(error, slug);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async findSlugsNotAdded() {
|
|
42
|
+
const slugsNotAdded = [];
|
|
43
|
+
if (!fs.pathExistsSync(this._repositoryName)) {
|
|
44
|
+
return slugsNotAdded;
|
|
45
|
+
}
|
|
46
|
+
for (const fileName of fs.readdirSync(this._repositoryName)) {
|
|
47
|
+
if (!fileName.endsWith(YAML_EXTENSION)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const slug = path.basename(fileName, YAML_EXTENSION);
|
|
51
|
+
if (await this.workspaceConfig.exists('i18n', slug)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Only `pull` writes an inherited file, and it registers it as it does. One left behind by a
|
|
55
|
+
// workspace that no longer inherits it is stale, not a locale waiting to be tracked.
|
|
56
|
+
if (slug.endsWith(types_1.Locale.INHERITED_SUFFIX)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (this.validSlug(slug)) {
|
|
60
|
+
slugsNotAdded.push(slug);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
cli_1.default.log(color_1.default.yellow(`\n⚠️ local locale file '${fileName}' doesn't have a valid name and will be ignored. ${this.slugFormatMessage()}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return slugsNotAdded;
|
|
67
|
+
}
|
|
68
|
+
getResourcePath(locale) {
|
|
69
|
+
return path.join(this._repositoryName, `${locale.slug ?? locale.locale}${YAML_EXTENSION}`);
|
|
70
|
+
}
|
|
71
|
+
async removeFiles(locale) {
|
|
72
|
+
fs.removeSync(this.getResourcePath(locale));
|
|
73
|
+
this.removeDirectoryIfEmpty(this._repositoryName);
|
|
74
|
+
}
|
|
75
|
+
resourceToMetadata(locale) {
|
|
76
|
+
return {
|
|
77
|
+
id: locale.id,
|
|
78
|
+
locale: locale.locale,
|
|
79
|
+
slug: locale.slug,
|
|
80
|
+
...(locale.resolvingTeamSlug && { resolvingTeamSlug: locale.resolvingTeamSlug }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async saveFiles(locale) {
|
|
84
|
+
// Inherited locales are written read-only, like inherited modules: they belong to a parent
|
|
85
|
+
// workspace, and editing one here would push an override rather than change the parent.
|
|
86
|
+
(0, file_utils_1.outputFileSyncIfChanged)(this.getResourcePath(locale), locale.content || '', { readOnly: Boolean(locale.resolvingTeamSlug) });
|
|
87
|
+
}
|
|
88
|
+
async metadataFromFiles(slug) {
|
|
89
|
+
return {
|
|
90
|
+
locale: slug,
|
|
91
|
+
slug: slug,
|
|
92
|
+
contentHash: '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
validSlug(slug) {
|
|
96
|
+
return types_1.Locale.LOCALE_REGEX.test(slug);
|
|
97
|
+
}
|
|
98
|
+
slugFormatMessage() {
|
|
99
|
+
return "Use up to 20 letters, numbers, '-' or '_', starting with a letter or a number.";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.default = LocaleRepository;
|
|
@@ -19,7 +19,10 @@ class ResourceRepositoryBase {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
async findAll() {
|
|
22
|
-
|
|
22
|
+
// Defaulted because a collection added after a workspace was cloned (as `i18n` was) is absent
|
|
23
|
+
// from its sync metadata until the first pull writes it — findAll must see "no resources",
|
|
24
|
+
// not crash every aggregate pull/push/status in every pre-existing workspace.
|
|
25
|
+
const { [this._repositoryName]: resources = {} } = await this.workspaceConfig.getActiveTeam();
|
|
23
26
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
24
27
|
const existResources = await Object.keys(resources).reduce((promise, slug) => promise.then(async (existIds) => {
|
|
25
28
|
const exists = await this.exists(slug);
|
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
* Reads/writes the singleton team.json (timezone, error handler, inheritance, webhook auth) at the
|
|
@@ -16,6 +16,12 @@ 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;
|
|
@@ -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), 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
|
+
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, this.parseWebhookAuth(raw.webhookAuth));
|
|
54
|
+
return new types_1.Team(raw.slug, raw.parentTeamSlugs ?? [], raw.zoneId, errorHandlerConfig, this.parseWebhookAuth(raw.webhookAuth), raw.primaryLocale ?? undefined);
|
|
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();
|
|
@@ -84,6 +111,16 @@ class TeamRepository {
|
|
|
84
111
|
if (team.webhookAuth !== undefined) {
|
|
85
112
|
file.webhookAuth = team.webhookAuth;
|
|
86
113
|
}
|
|
114
|
+
if (team.primaryLocale !== undefined) {
|
|
115
|
+
file.primaryLocale = team.primaryLocale;
|
|
116
|
+
}
|
|
117
|
+
// Pull-only global versioning fields, appended after the pushable settings.
|
|
118
|
+
if (team.versions !== undefined) {
|
|
119
|
+
file.versions = team.versions;
|
|
120
|
+
}
|
|
121
|
+
if (team.aliases !== undefined) {
|
|
122
|
+
file.aliases = team.aliases;
|
|
123
|
+
}
|
|
87
124
|
return file;
|
|
88
125
|
}
|
|
89
126
|
}
|
|
@@ -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;
|