@omega.js/desktop 0.53.0 → 0.54.1
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 +38 -38
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +2 -3
- package/dist/commands/dev.js +25 -0
- package/dist/commands/lib/ensure-target.js +12 -17
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/release.js +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/commands/update.js +5 -4
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +36 -36
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +1 -1
- package/dist/defaults/hooks/build/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +1 -1
- package/dist/defaults/hooks/release/pre.js +1 -1
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +11 -11
- package/dist/defaults/src/integrations/menu/index.js +5 -5
- package/dist/defaults/src/integrations/tray/index.js +9 -9
- package/dist/defaults/src/main.js +2 -2
- package/dist/defaults/src/preload.js +1 -1
- package/dist/defaults/test/README.md +3 -3
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/tasks/audit.js +5 -8
- package/dist/lib/restart-manager/index.js +1 -1
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/main.js +4 -3
- package/dist/preload.js +1 -1
- package/dist/test/suites/build/audit.test.js +20 -7
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +28 -0
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +4 -2
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +11 -3
- package/dist/test/suites/build/merge-line-files.test.js +6 -6
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +9 -8
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/validate-config.test.js +13 -2
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/runner-env.js +13 -28
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/index.js +13 -17
- package/dist/vendor/config/load.js +15 -7
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +99 -152
- package/dist/vendor/config/validate.js +97 -77
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +15 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +13 -7
- package/dist/vendor/devkit/defaults-engine.js +9 -43
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +62 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -176
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/package.json +18 -24
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -119
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auth.md +0 -284
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -44
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -84
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -109
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -328
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -227
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -917
- package/docs/shared/config.md +0 -1948
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -342
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
|
@@ -64,6 +64,12 @@
|
|
|
64
64
|
* refinements, seeded from real brand configs, never from guesses.
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
|
+
// Rule sets kept in their own files, one concern each.
|
|
68
|
+
const { MANAGER_RULES } = require('./schema-manager.js');
|
|
69
|
+
const { CLIENT_RULES } = require('./schema-client.js');
|
|
70
|
+
const { CLOUD_CONFIG_RULES } = require('./schema-cloud.js');
|
|
71
|
+
const { OVERRIDE_RULES } = require('./schema-overrides.js');
|
|
72
|
+
|
|
67
73
|
// The durations a winback coupon can be built for on every provider that
|
|
68
74
|
// supports the offer (#268). Stripe's third option ('repeating') needs a
|
|
69
75
|
// duration_in_months beside it, which is provider surface nothing here asks
|
|
@@ -201,13 +207,14 @@ const CLIENT_SECTIONS = {
|
|
|
201
207
|
const BRAND_ID_PATTERN = /^[a-z][a-z0-9+\-.]*$/;
|
|
202
208
|
|
|
203
209
|
const SHARED_SCHEMA = [
|
|
210
|
+
...MANAGER_RULES,
|
|
204
211
|
// ── the target's own url ─────────────────────────────────────────────────
|
|
205
212
|
{
|
|
206
213
|
path: 'url',
|
|
207
214
|
type: 'string',
|
|
208
215
|
required: false,
|
|
209
216
|
match: /^https?:\/\//,
|
|
210
|
-
description: "This TARGET's own public URL, and the one key a target entry may set at the top level. A target whose name is not its type derives it from the name (`admin: { type: 'web' }` → https://admin.<host of brand.url
|
|
217
|
+
description: "This TARGET's own public URL, and the one key a target entry may set at the top level. A target whose name is not its type derives it from the name (`admin: { type: 'web' }` → https://admin.<host of brand.url>; docs/shared/config.md → Targets); an entry's own `url` overrides it for a custom host. `brand.url` stays the BRAND's url, which is what brand-level facts (authDomain, the persona domain) read.",
|
|
211
218
|
},
|
|
212
219
|
|
|
213
220
|
// ── the target's own type (#886) ─────────────────────────────────────────
|
|
@@ -220,7 +227,7 @@ const SHARED_SCHEMA = [
|
|
|
220
227
|
type: 'string',
|
|
221
228
|
required: false,
|
|
222
229
|
enum: [...TARGETS, CUSTOM_TARGET_TYPE],
|
|
223
|
-
description: "The FRAMEWORK that runs this target
|
|
230
|
+
description: "The FRAMEWORK that runs this target, or `custom` for a target no framework owns. Key presence in `targets` enables a target by NAME, and this is what says which code runs there, so a target named `admin` with `type: 'web'` is a web build. A resolved target config carries it at the top level like every other key in the entry.",
|
|
224
231
|
},
|
|
225
232
|
|
|
226
233
|
// ── brand ────────────────────────────────────────────────────────────────
|
|
@@ -260,7 +267,7 @@ const SHARED_SCHEMA = [
|
|
|
260
267
|
path: 'brand.type',
|
|
261
268
|
type: 'string',
|
|
262
269
|
required: false,
|
|
263
|
-
description: "The brand's schema.org type ('Organization', 'Corporation', 'LocalBusiness'). @omega.js/web stamps it on the JSON-LD brand node and every @id that cross-references it; unset renders the framework default 'Organization'
|
|
270
|
+
description: "The brand's schema.org type ('Organization', 'Corporation', 'LocalBusiness'). @omega.js/web stamps it on the JSON-LD brand node and every @id that cross-references it; unset renders the framework default 'Organization'.",
|
|
264
271
|
},
|
|
265
272
|
{
|
|
266
273
|
path: 'brand.font',
|
|
@@ -285,7 +292,7 @@ const SHARED_SCHEMA = [
|
|
|
285
292
|
path: 'brand.contact.person.name',
|
|
286
293
|
type: 'string',
|
|
287
294
|
required: false,
|
|
288
|
-
description: 'The human who signs "personal" emails, as it should read ("Jane Doe, CEO"). Required only when something sends a personal-signoff email
|
|
295
|
+
description: 'The human who signs "personal" emails, as it should read ("Jane Doe, CEO"). Required only when something sends a personal-signoff email; that path fails loudly rather than substituting a framework identity.',
|
|
289
296
|
},
|
|
290
297
|
{
|
|
291
298
|
path: 'brand.contact.person.firstName',
|
|
@@ -329,7 +336,7 @@ const SHARED_SCHEMA = [
|
|
|
329
336
|
type: 'string',
|
|
330
337
|
required: false,
|
|
331
338
|
match: /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,
|
|
332
|
-
description:
|
|
339
|
+
description: "Brand accent color as a hex string, #RGB or #RRGGBB. @omega.js/web composes the --omega-accent ramps (light + dark) from it; unset leaves the theme's neutral placeholder standing.",
|
|
333
340
|
},
|
|
334
341
|
{
|
|
335
342
|
path: 'brand.images',
|
|
@@ -343,7 +350,7 @@ const SHARED_SCHEMA = [
|
|
|
343
350
|
path: 'socials',
|
|
344
351
|
type: 'object',
|
|
345
352
|
required: false,
|
|
346
|
-
description: "Platform → handle ({ twitter: 'somiibo' }). The handle derives the profile URL every surface reads (JSON-LD sameAs, the footer row, omega_social), and @omega.js/web emits a redirect shortlink page at /<platform> for each entry
|
|
353
|
+
description: "Platform → handle ({ twitter: 'somiibo' }). The handle derives the profile URL every surface reads (JSON-LD sameAs, the footer row, omega_social), and @omega.js/web emits a redirect shortlink page at /<platform> for each entry. An entry that redirects somewhere OTHER than its profile URL takes the object form { handle, redirect }: the redirect target wins for the shortlink, the handle still names the profile.",
|
|
347
354
|
},
|
|
348
355
|
|
|
349
356
|
// ── cloud (role: app/cloud platform; D12 provider-discriminated) ─────────
|
|
@@ -352,25 +359,14 @@ const SHARED_SCHEMA = [
|
|
|
352
359
|
type: 'string',
|
|
353
360
|
required: false,
|
|
354
361
|
enum: ['firebase'],
|
|
355
|
-
description: "App/cloud platform provider. Only 'firebase' today
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
path: 'cloud.config',
|
|
359
|
-
type: 'object',
|
|
360
|
-
required: false,
|
|
361
|
-
description: 'Provider app config. For firebase: the web-app config verbatim from the console. Public by design — the web API key is not a secret.',
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
path: 'cloud.config.projectId',
|
|
365
|
-
type: 'string',
|
|
366
|
-
required: false,
|
|
367
|
-
description: 'Drives auth, emulator project selection, analytics uuidv5 namespace, remote-config URL fallbacks.',
|
|
362
|
+
description: "App/cloud platform provider. Only 'firebase' today; the discriminator exists so a second provider slots in without a key rename.",
|
|
368
363
|
},
|
|
364
|
+
...CLOUD_CONFIG_RULES,
|
|
369
365
|
{
|
|
370
366
|
path: 'cloud.messaging.vapidKey',
|
|
371
367
|
type: 'string',
|
|
372
368
|
required: false,
|
|
373
|
-
description: 'Web-push VAPID public key (Firebase console → Cloud Messaging → Web Push certificates). Public by design
|
|
369
|
+
description: 'Web-push VAPID public key (Firebase console → Cloud Messaging → Web Push certificates). Public by design: it ships to every browser, and the private half stays in the console.',
|
|
374
370
|
},
|
|
375
371
|
{
|
|
376
372
|
path: 'cloud.shared',
|
|
@@ -384,19 +380,19 @@ const SHARED_SCHEMA = [
|
|
|
384
380
|
type: 'string',
|
|
385
381
|
required: false,
|
|
386
382
|
match: /@/,
|
|
387
|
-
description: "OAuth consent screen support email. Defaults to the AUTHORIZING user's email
|
|
383
|
+
description: "OAuth consent screen support email. Defaults to the AUTHORIZING user's email, because the provider rejects any address the caller doesn't own; set only for an owned Google Group.",
|
|
388
384
|
},
|
|
389
385
|
{
|
|
390
386
|
path: 'cloud.oauthRedirectsConfigured',
|
|
391
387
|
type: 'boolean',
|
|
392
388
|
required: false,
|
|
393
|
-
description: "true = the Google OAuth client's authorized origins + redirect URIs are configured by hand. The ONE reconcile flag config keeps
|
|
389
|
+
description: "true = the Google OAuth client's authorized origins + redirect URIs are configured by hand. The ONE reconcile flag config keeps: that surface has no API to re-check, so the cloud service asks once, writes the answer back here, and every later run trusts it. Never set it without doing the work: the confirm is the only proof there is.",
|
|
394
390
|
},
|
|
395
391
|
{
|
|
396
392
|
path: 'cloud.consentAudience',
|
|
397
393
|
type: 'boolean',
|
|
398
394
|
required: false,
|
|
399
|
-
description: "false = stop asking about the OAuth consent screen's audience.
|
|
395
|
+
description: "false = stop asking about the OAuth consent screen's audience. A tri-state opt-out: Google gives NO API write for the audience, so an Internal screen (Error 403: org_internal for every non-org account) is a manage-time stopper that opens the console page and polls; `false` records the deliberate choice to live with an org-only sign-in, and only the cloud service's audience step goes quiet.",
|
|
400
396
|
},
|
|
401
397
|
{
|
|
402
398
|
path: 'cloud.apiSubdomain',
|
|
@@ -409,13 +405,13 @@ const SHARED_SCHEMA = [
|
|
|
409
405
|
path: 'cloud.organizationId',
|
|
410
406
|
type: 'string|boolean',
|
|
411
407
|
required: false,
|
|
412
|
-
description: 'Cloud organization ID the project is created inside
|
|
408
|
+
description: 'Cloud organization ID the project is created inside, a tri-state: unset = ask at project create, false = no org (standalone), value = create inside it.',
|
|
413
409
|
},
|
|
414
410
|
{
|
|
415
411
|
path: 'cloud.billingAccount',
|
|
416
412
|
type: 'string|boolean',
|
|
417
413
|
required: false,
|
|
418
|
-
description: "Billing account ('billingAccounts/XXXXXX-XXXXXX-XXXXXX')
|
|
414
|
+
description: "Billing account ('billingAccounts/XXXXXX-XXXXXX-XXXXXX'), a tri-state: unset = ask, false = stay on the free tier, value = auto-upgrade.",
|
|
419
415
|
},
|
|
420
416
|
|
|
421
417
|
// ── analytics ────────────────────────────────────────────────────────────
|
|
@@ -430,13 +426,13 @@ const SHARED_SCHEMA = [
|
|
|
430
426
|
path: 'analytics.providers.google.propertyId',
|
|
431
427
|
type: 'string',
|
|
432
428
|
required: false,
|
|
433
|
-
description:
|
|
429
|
+
description: "GA4 property id (digits, as the Admin API returns it). The manager's analytics service reconciles the brand's data streams and the Firebase link against it, and it gates the whole google half: no propertyId, no google operations. Interactive setup selects/creates the property and writes it back here.",
|
|
434
430
|
},
|
|
435
431
|
{
|
|
436
432
|
path: 'analytics.providers.google.accountId',
|
|
437
433
|
type: 'string',
|
|
438
434
|
required: false,
|
|
439
|
-
description: "GA account id the property lives under (digits)
|
|
435
|
+
description: "GA account id the property lives under (digits), read by the analytics property picker and the console bookmarks. A company-managed brand inherits the COMPANY layer's value through the merge chain; a brand-level value always wins.",
|
|
440
436
|
},
|
|
441
437
|
{
|
|
442
438
|
path: 'analytics.providers.meta.id',
|
|
@@ -448,7 +444,7 @@ const SHARED_SCHEMA = [
|
|
|
448
444
|
path: 'analytics.providers.meta.accountId',
|
|
449
445
|
type: 'string',
|
|
450
446
|
required: false,
|
|
451
|
-
description: "Meta AD ACCOUNT id ('act_…' or the bare digits) the pixel is created on and found by name against. A non-secret platform id, so it lives here beside google.accountId
|
|
447
|
+
description: "Meta AD ACCOUNT id ('act_…' or the bare digits) the pixel is created on and found by name against. A non-secret platform id, so it lives here beside google.accountId; the token that reaches the account stays in .env (META_ACCESS_TOKEN).",
|
|
452
448
|
},
|
|
453
449
|
{
|
|
454
450
|
path: 'analytics.providers.tiktok.id',
|
|
@@ -466,7 +462,7 @@ const SHARED_SCHEMA = [
|
|
|
466
462
|
path: 'analytics.providers.tiktok.appId',
|
|
467
463
|
type: 'string',
|
|
468
464
|
required: false,
|
|
469
|
-
description:
|
|
465
|
+
description: 'TikTok DEVELOPER APP id the access-token mint authorizes through. Public config, so the manager can walk the portal without asking twice; setup writes it back here. The app secret is pasted once and never saved, and the token lands in .env (TIKTOK_ACCESS_TOKEN).',
|
|
470
466
|
},
|
|
471
467
|
|
|
472
468
|
// ── advertising ──────────────────────────────────────────────────────────
|
|
@@ -474,7 +470,7 @@ const SHARED_SCHEMA = [
|
|
|
474
470
|
path: 'advertising.providers.adsense.client',
|
|
475
471
|
type: 'string',
|
|
476
472
|
required: false,
|
|
477
|
-
description: 'AdSense publisher client id (ca-pub-…). The ONE adsense switch
|
|
473
|
+
description: 'AdSense publisher client id (ca-pub-…). The ONE adsense switch, presence-driven: set it and the manager service manages the account, the site renders units from it and ads.txt carries the record; leave it out and none of that happens. No second gate exists, so a managed-but-ad-free brand is deliberately inexpressible.',
|
|
478
474
|
},
|
|
479
475
|
{
|
|
480
476
|
path: 'advertising.providers.adsense.displaySlot',
|
|
@@ -510,13 +506,13 @@ const SHARED_SCHEMA = [
|
|
|
510
506
|
path: 'advertising.fallback',
|
|
511
507
|
type: 'string|boolean',
|
|
512
508
|
required: false,
|
|
513
|
-
description: "The role a provider miss falls through to
|
|
509
|
+
description: "The role a provider miss falls through to: 'inhouse' renders the house unit, false (or absent) ends the ladder at the built-in promo. Role-level, beside `providers` (docs/web/ads-system.md).",
|
|
514
510
|
},
|
|
515
511
|
{
|
|
516
512
|
path: 'advertising.tags',
|
|
517
513
|
type: 'array',
|
|
518
514
|
required: false,
|
|
519
|
-
description: "This brand's contextual tags (['music', 'audio-tools'])
|
|
515
|
+
description: "This brand's contextual tags (['music', 'audio-tools']), the targeting match input a house unit sends with its serve request unless the include passes its own. No user tracking.",
|
|
520
516
|
},
|
|
521
517
|
|
|
522
518
|
// ── company (ONE typed key, the rest RESOLVED) ───────────────────────────
|
|
@@ -543,7 +539,7 @@ const SHARED_SCHEMA = [
|
|
|
543
539
|
// would be noise, and the fact belongs to the brands that actually share
|
|
544
540
|
// somebody else's provider account.
|
|
545
541
|
materialize: false,
|
|
546
|
-
description: "Whether the manage walk may repoint this company's provider ACCOUNT webhooks
|
|
542
|
+
description: "Whether the manage walk may repoint this company's provider ACCOUNT webhooks. `false` says the SendGrid Event Webhook and the Beehiiv webhook belong to someone else (a shared account whose one account-level webhook points at their production), so the campaigns and newsletter services leave them alone.",
|
|
547
543
|
},
|
|
548
544
|
|
|
549
545
|
// ── features (the catalog every product prices) ──────────────────────────
|
|
@@ -551,7 +547,7 @@ const SHARED_SCHEMA = [
|
|
|
551
547
|
path: 'features',
|
|
552
548
|
type: 'object',
|
|
553
549
|
required: false,
|
|
554
|
-
description: "The feature catalog
|
|
550
|
+
description: "The feature catalog: every feature DEFINED once, keyed by id, in the order every surface renders it. An entry carries name, icon (the full Font Awesome class string) and definition, plus, when the feature is METERED, a `usage` block ({ pace: 'daily' | false, mirror: ['<doc kind>'] }); an entry without one is a perk. Products name only the VALUE (payment.products[].features). Key order is row order on the pricing page and the account's usage bars.",
|
|
555
551
|
},
|
|
556
552
|
|
|
557
553
|
// ── payment (role: billing; provider-discriminated) ──────────────────────
|
|
@@ -560,7 +556,7 @@ const SHARED_SCHEMA = [
|
|
|
560
556
|
type: 'string',
|
|
561
557
|
required: false,
|
|
562
558
|
default: 'USD',
|
|
563
|
-
description: "The ISO 4217 code every price in payment.products is quoted in
|
|
559
|
+
description: "The ISO 4217 code every price in payment.products is quoted in. One currency per brand, named wherever a price is shown or recorded: the pricing page's JSON-LD, the checkout, and the backend's order history (GET /user/orders). Defaults to 'USD'.",
|
|
564
560
|
},
|
|
565
561
|
{
|
|
566
562
|
path: 'payment.providers.stripe.publishableKey',
|
|
@@ -586,20 +582,20 @@ const SHARED_SCHEMA = [
|
|
|
586
582
|
type: 'boolean',
|
|
587
583
|
required: false,
|
|
588
584
|
default: false,
|
|
589
|
-
description:
|
|
585
|
+
description: 'Coinbase Commerce (crypto) checkout, one-time purchases only. Its ONLY credential is a secret (COINBASE_COMMERCE_API_KEY), so there is no public datum to gate on: this is the switch, default OFF, because an accidental ON shows a Crypto button no key can complete.',
|
|
590
586
|
},
|
|
591
587
|
{
|
|
592
588
|
path: 'payment.products',
|
|
593
589
|
type: 'array',
|
|
594
590
|
required: false,
|
|
595
|
-
description: 'Product catalog (@omega.js/backend-shaped: id, name, type, prices, per-provider IDs)
|
|
591
|
+
description: 'Product catalog (@omega.js/backend-shaped: id, name, type, prices, per-provider IDs), referenceable from every target and the ONLY pricing-page source. Optional presentation fields: tagline, popular, enterprise (the talk-to-us tier: its own full-width row, never a card), hidden (still created on every provider and purchasable by id, as QA tiers and grandfathered plans are, but never rendered on the pricing page), url, and features { <catalog id>: value }, a number on a counted feature (the MONTHLY limit, -1 unlimited) or true/false/a string on a perk.',
|
|
596
592
|
},
|
|
597
593
|
{
|
|
598
594
|
path: 'payment.winback.enabled',
|
|
599
595
|
type: 'boolean',
|
|
600
596
|
required: false,
|
|
601
597
|
default: true,
|
|
602
|
-
description: 'The cancel-flow save offer
|
|
598
|
+
description: 'The cancel-flow save offer, shown before the cancellation questionnaire. Defaults ON; false is the whole off switch, and the cancel flow goes straight to the questionnaire.',
|
|
603
599
|
},
|
|
604
600
|
{
|
|
605
601
|
path: 'payment.winback.percent',
|
|
@@ -638,7 +634,7 @@ const SHARED_SCHEMA = [
|
|
|
638
634
|
path: 'monitoring.providers.sentry.org',
|
|
639
635
|
type: 'string',
|
|
640
636
|
required: false,
|
|
641
|
-
description: 'Sentry organization slug (public). Auto-detected when the auth token sees exactly one org
|
|
637
|
+
description: 'Sentry organization slug (public). Auto-detected when the auth token sees exactly one org; the monitoring service writes it back here.',
|
|
642
638
|
},
|
|
643
639
|
{
|
|
644
640
|
path: 'monitoring.providers.sentry.dsn',
|
|
@@ -675,7 +671,7 @@ const SHARED_SCHEMA = [
|
|
|
675
671
|
required: false,
|
|
676
672
|
min: 0,
|
|
677
673
|
max: 1,
|
|
678
|
-
description: 'Browser only: fraction of SESSIONS recorded as a replay, 0..1. Defaults to 0
|
|
674
|
+
description: 'Browser only: fraction of SESSIONS recorded as a replay, 0..1. Defaults to 0, because replay costs bandwidth and captures the DOM, so it is strictly opt-in; any value above 0 loads the replay integration.',
|
|
679
675
|
},
|
|
680
676
|
{
|
|
681
677
|
path: 'monitoring.providers.sentry.replaysOnErrorSampleRate',
|
|
@@ -701,7 +697,7 @@ const SHARED_SCHEMA = [
|
|
|
701
697
|
path: 'monitoring.providers.sentry.bundlePatterns',
|
|
702
698
|
type: 'array',
|
|
703
699
|
required: false,
|
|
704
|
-
description: "Browser only: the URL fragments that identify OUR bundles
|
|
700
|
+
description: "Browser only: the URL fragments that identify OUR bundles; a browser event reports only when a stack frame matches one. Defaults to ['/assets/js/'], where both @omega.js/web and @omega.js/extension serve every framework bundle.",
|
|
705
701
|
},
|
|
706
702
|
|
|
707
703
|
// ── connections ──────────────────────────────────────────────────────────
|
|
@@ -755,7 +751,7 @@ const SHARED_SCHEMA = [
|
|
|
755
751
|
description: 'Connect your Kick channel',
|
|
756
752
|
},
|
|
757
753
|
},
|
|
758
|
-
description: "Per-provider user-connection settings, keyed by provider name (`connections: { twitch: {…} }`)
|
|
754
|
+
description: "Per-provider user-connection settings, keyed by provider name (`connections: { twitch: {…} }`), public values only, never client secrets (those are the CONNECTIONS_<PROVIDER>_CLIENT_ID/_SECRET env pair). The set of providers is OPEN (a brand ships its own as `src/connections/<name>.js`), so the section stays free-form; the keys every entry may carry are `enabled` (the packaged providers default to false, so set it true to offer one; a brand's own provider is on unless this is false), `scope` (an array that wins over the provider module's default), `name` and `logo` (what the account page's card draws; the CONFIG is the only card list, and `logo` is the name of a mark @omega.js/web ships in core/logos/brandmarks/original, rendered inline, or a full URL rendered as an img), and `description` (the line under the card's title). The five packaged providers carry all three by default, so enabling one is one line. docs/backend/connections.md",
|
|
759
755
|
},
|
|
760
756
|
|
|
761
757
|
// ── repo (where the brand hosts its source; #883) ───────────────────────
|
|
@@ -767,7 +763,7 @@ const SHARED_SCHEMA = [
|
|
|
767
763
|
path: 'repo',
|
|
768
764
|
type: 'object',
|
|
769
765
|
required: false,
|
|
770
|
-
description: 'Where the brand hosts its source. Presence enables the repo service; the block is provider + org and nothing else, since every repo name derives from `<brand.id>-<role
|
|
766
|
+
description: 'Where the brand hosts its source. Presence enables the repo service; the block is provider + org and nothing else, since every repo name derives from `<brand.id>-<role>`.',
|
|
771
767
|
},
|
|
772
768
|
{
|
|
773
769
|
path: 'repo.provider',
|
|
@@ -794,19 +790,19 @@ const SHARED_SCHEMA = [
|
|
|
794
790
|
path: 'domain.providers',
|
|
795
791
|
type: 'object',
|
|
796
792
|
required: false,
|
|
797
|
-
description: "The brand's REGISTRAR, named as the one KEY under it ({ namecheap: {} })
|
|
793
|
+
description: "The brand's REGISTRAR, named as the one KEY under it ({ namecheap: {} }). Presence picks it, and no entry means nothing chosen, so the domain service skips. The provider set is open; the entry may be empty.",
|
|
798
794
|
},
|
|
799
795
|
{
|
|
800
796
|
path: 'domain.providers.namecheap',
|
|
801
797
|
type: 'object',
|
|
802
798
|
required: false,
|
|
803
|
-
description: 'Namecheap as the registrar
|
|
799
|
+
description: 'Namecheap as the registrar, the one provider whose nameservers the domain service points at the Cloudflare zone via API (NAMECHEAP_USERNAME + NAMECHEAP_API_KEY in .env). An empty object is the whole declaration; every other registrar gets manual instructions.',
|
|
804
800
|
},
|
|
805
801
|
{
|
|
806
802
|
path: 'domain.email.providers',
|
|
807
803
|
type: 'object',
|
|
808
804
|
required: false,
|
|
809
|
-
description: "The brand's MAILBOX provider, named as the one KEY under it ({ cloudflare: {} } | { squarespace: {} } | { privateemail: {} })
|
|
805
|
+
description: "The brand's MAILBOX provider, named as the one KEY under it ({ cloudflare: {} } | { squarespace: {} } | { privateemail: {} }). Presence picks it; no entry leaves the edge service's email-routing operations off.",
|
|
810
806
|
},
|
|
811
807
|
{
|
|
812
808
|
path: 'domain.email.forwarding',
|
|
@@ -821,11 +817,11 @@ const SHARED_SCHEMA = [
|
|
|
821
817
|
type: 'boolean',
|
|
822
818
|
required: false,
|
|
823
819
|
default: true,
|
|
824
|
-
description: 'false = the edge service skips entirely
|
|
820
|
+
description: 'false = the edge service skips entirely: no zone, DNS, settings, rules, speed-test or worker reconciliation for this brand.',
|
|
825
821
|
},
|
|
826
822
|
{
|
|
827
823
|
path: 'edge.providers.cloudflare',
|
|
828
|
-
type: 'object',
|
|
824
|
+
type: 'object', open: true,
|
|
829
825
|
required: false,
|
|
830
826
|
description: 'Cloudflare zone reconciliation (@omega.js/manager cloudflare service): dns, settings, rules, cacheRules, speedTest, workers. Tokens live in .env.',
|
|
831
827
|
},
|
|
@@ -839,7 +835,7 @@ const SHARED_SCHEMA = [
|
|
|
839
835
|
path: 'edge.providers.cloudflare.rules.redirect',
|
|
840
836
|
type: 'array',
|
|
841
837
|
required: false,
|
|
842
|
-
description: "The zone's dynamic redirect rules, ORDERED
|
|
838
|
+
description: "The zone's dynamic redirect rules, ORDERED, and the ONE home for a TEMPLATED redirect, i.e. one whose destination is computed from the request path: [{ name, expression, statusCode, preserveQueryString, targetUrl, enabled }]. `expression` and `targetUrl` ({ value } for a fixed URL, { expression } for a computed one) are Cloudflare's own filter language, because only the edge can answer a URL the build cannot enumerate: DashQR's printed `/c/<id>` codes redirect to `/code?id=<id>` with `targetUrl.expression: concat(\"https://\", http.host, \"/code?id=\", substring(http.request.uri.path, 3))`. The @omega.js/manager edge service reconciles them by `name`. A redirect whose URLs CAN be enumerated is a redirect PAGE instead (docs/web/index.md), never config.",
|
|
843
839
|
},
|
|
844
840
|
|
|
845
841
|
// ── captcha (role: bot defense; provider-discriminated) ──────────────────
|
|
@@ -847,13 +843,13 @@ const SHARED_SCHEMA = [
|
|
|
847
843
|
path: 'captcha.providers.recaptcha.project',
|
|
848
844
|
type: 'string',
|
|
849
845
|
required: false,
|
|
850
|
-
description: "The brand's OWN GCP project hosting the classic reCAPTCHA key
|
|
846
|
+
description: "The brand's OWN GCP project hosting the classic reCAPTCHA key, used only for the console deep-link in guidance. Site/secret keys stay in .env.",
|
|
851
847
|
},
|
|
852
848
|
{
|
|
853
849
|
path: 'captcha.providers.recaptcha.siteKey',
|
|
854
850
|
type: 'string',
|
|
855
851
|
required: false,
|
|
856
|
-
description: 'Classic reCAPTCHA site key rendered client-side. Public by design
|
|
852
|
+
description: 'Classic reCAPTCHA site key rendered client-side. Public by design; the secret half stays in .env (RECAPTCHA_SECRET_KEY).',
|
|
857
853
|
},
|
|
858
854
|
{
|
|
859
855
|
path: 'captcha.providers.recaptcha.domainsConfirmed',
|
|
@@ -874,7 +870,7 @@ const SHARED_SCHEMA = [
|
|
|
874
870
|
path: 'forms.providers.slapform.formId',
|
|
875
871
|
type: 'string',
|
|
876
872
|
required: false,
|
|
877
|
-
description: 'Slapform form id
|
|
873
|
+
description: 'Slapform form id; the contact page posts to https://api.slapform.com/{formId}. Interactive manager runs land it here.',
|
|
878
874
|
},
|
|
879
875
|
{
|
|
880
876
|
path: 'forms.providers.slapform.templateFormId',
|
|
@@ -894,7 +890,7 @@ const SHARED_SCHEMA = [
|
|
|
894
890
|
type: 'object',
|
|
895
891
|
required: false,
|
|
896
892
|
default: { id: 'grandmaster', name: 'Grandmaster' },
|
|
897
|
-
description: 'Tier granted to the form-owner account ({ id, name })
|
|
893
|
+
description: 'Tier granted to the form-owner account ({ id, name }), for Slapform operators only.',
|
|
898
894
|
},
|
|
899
895
|
|
|
900
896
|
// ── inbound (role: inbound conversations; per-channel providers) ─────────
|
|
@@ -915,7 +911,7 @@ const SHARED_SCHEMA = [
|
|
|
915
911
|
path: 'inbound.chat.providers.chatsy.accountId',
|
|
916
912
|
type: 'string',
|
|
917
913
|
required: false,
|
|
918
|
-
description: "Chatsy owner-account uid the agent belongs to
|
|
914
|
+
description: "Chatsy owner-account uid the agent belongs to, the account whose subscription the chat service reconciles to `plan`. The service resolves it from the agent itself; set it only when the agent's owner is provisioned elsewhere.",
|
|
919
915
|
},
|
|
920
916
|
{
|
|
921
917
|
path: 'inbound.chat.providers.chatsy.templateAgentId',
|
|
@@ -935,7 +931,7 @@ const SHARED_SCHEMA = [
|
|
|
935
931
|
type: 'object',
|
|
936
932
|
required: false,
|
|
937
933
|
default: { id: 'max', name: 'Max' },
|
|
938
|
-
description: 'Tier granted to the agent-owner account ({ id, name })
|
|
934
|
+
description: 'Tier granted to the agent-owner account ({ id, name }), for Chatsy operators only.',
|
|
939
935
|
},
|
|
940
936
|
{
|
|
941
937
|
path: 'inbound.chat.providers.chatsy.sponsorshipsUrl',
|
|
@@ -980,7 +976,7 @@ const SHARED_SCHEMA = [
|
|
|
980
976
|
type: 'object',
|
|
981
977
|
required: false,
|
|
982
978
|
default: { id: 'max', name: 'Max' },
|
|
983
|
-
description: 'Tier granted to the agent-owner account ({ id, name })
|
|
979
|
+
description: 'Tier granted to the agent-owner account ({ id, name }), for Replyify operators only.',
|
|
984
980
|
},
|
|
985
981
|
{
|
|
986
982
|
path: 'inbound.email.providers.replyify.discount',
|
|
@@ -995,7 +991,7 @@ const SHARED_SCHEMA = [
|
|
|
995
991
|
type: 'boolean',
|
|
996
992
|
required: false,
|
|
997
993
|
default: true,
|
|
998
|
-
description: 'false = the search service skips entirely
|
|
994
|
+
description: 'false = the search service skips entirely: the property is never verified and no sitemap is submitted (submitSitemap only turns the sitemap half off).',
|
|
999
995
|
},
|
|
1000
996
|
{
|
|
1001
997
|
path: 'search.providers.searchConsole.submitSitemap',
|
|
@@ -1023,13 +1019,13 @@ const SHARED_SCHEMA = [
|
|
|
1023
1019
|
path: 'certificates.enabled',
|
|
1024
1020
|
type: 'boolean',
|
|
1025
1021
|
required: false,
|
|
1026
|
-
description: 'false = the certificates service skips entirely
|
|
1022
|
+
description: 'false = the certificates service skips entirely: no bundle ids, no signing certificates, no provisioning profiles for this brand. Absent reads as ON for brands with a desktop/mobile target (the service still skips one with neither).',
|
|
1027
1023
|
},
|
|
1028
1024
|
{
|
|
1029
1025
|
path: 'certificates.providers.apple.bundleIdPrefix',
|
|
1030
1026
|
type: 'string',
|
|
1031
1027
|
required: false,
|
|
1032
|
-
description: "Reverse-DNS prefix the brand's bundle id is composed from ('com.mycompany' + brand.id → com.mycompany.my.brand). The brand's own answer
|
|
1028
|
+
description: "Reverse-DNS prefix the brand's bundle id is composed from ('com.mycompany' + brand.id → com.mycompany.my.brand). The brand's own answer: the onboard wizard seeds it and setup asks for it; unset, the Apple operations have no id to reconcile.",
|
|
1033
1029
|
},
|
|
1034
1030
|
{
|
|
1035
1031
|
path: 'certificates.providers.apple.capabilities',
|
|
@@ -1047,7 +1043,7 @@ const SHARED_SCHEMA = [
|
|
|
1047
1043
|
path: 'certificates.providers.apple.certificates',
|
|
1048
1044
|
type: 'array',
|
|
1049
1045
|
required: false,
|
|
1050
|
-
description: "Certificate types managed for the Apple Developer account ([{ type, manual }])
|
|
1046
|
+
description: "Certificate types managed for the Apple Developer account ([{ type, manual }]); `manual: true` marks the ones Apple's API cannot create, which the Account Holder downloads from the portal. Unset uses the framework set.",
|
|
1051
1047
|
},
|
|
1052
1048
|
|
|
1053
1049
|
// ── publishing (role: ship credentials; no provider) ─────────────────
|
|
@@ -1055,7 +1051,7 @@ const SHARED_SCHEMA = [
|
|
|
1055
1051
|
path: 'publishing.enabled',
|
|
1056
1052
|
type: 'boolean',
|
|
1057
1053
|
required: false,
|
|
1058
|
-
description: "The manage walk's ship-credential gate
|
|
1054
|
+
description: "The manage walk's ship-credential gate: the `publishing` service asks for every developer key and listing id the brand's declared formats need (`targets.<name>.platforms.<platform>.formats`). `false` is the permanent opt-out the gate's Disable lands; absence means ask, and no default is materialized. Dropping ONE store is the declaration's job (`platforms.<store>.formats.store: false`), never this switch.",
|
|
1059
1055
|
},
|
|
1060
1056
|
|
|
1061
1057
|
// ── devlog (role: commit-digest publishing; provider-discriminated) ──────
|
|
@@ -1069,7 +1065,7 @@ const SHARED_SCHEMA = [
|
|
|
1069
1065
|
path: 'devlog.enabled',
|
|
1070
1066
|
type: 'boolean',
|
|
1071
1067
|
required: false,
|
|
1072
|
-
description:
|
|
1068
|
+
description: "true PUBLISHES AI-written commit-digest posts to the brand's live website. Case 3 (docs/shared/config.md): the literal true is the only ON, absence is off, and no default is materialized, so publishing is never implicit.",
|
|
1073
1069
|
},
|
|
1074
1070
|
|
|
1075
1071
|
// ── seo ──────────────────────────────────────────────────────────────────
|
|
@@ -1077,7 +1073,7 @@ const SHARED_SCHEMA = [
|
|
|
1077
1073
|
path: 'seo',
|
|
1078
1074
|
type: 'object',
|
|
1079
1075
|
required: false,
|
|
1080
|
-
description:
|
|
1076
|
+
description: "Parasite-SEO content: seo.github.content is the @omega.js/manager seo service's repos, and big content blocks may live in the config/seo.json5 sidecar. The indexing switch is NOT here: it is `targets.web.meta.index`, the same name a page writes.",
|
|
1081
1077
|
},
|
|
1082
1078
|
|
|
1083
1079
|
// ── account ──────────────────────────────────────────────────────────────
|
|
@@ -1085,7 +1081,7 @@ const SHARED_SCHEMA = [
|
|
|
1085
1081
|
path: 'account',
|
|
1086
1082
|
type: 'object',
|
|
1087
1083
|
required: false,
|
|
1088
|
-
description: "Managed Firebase Auth accounts (@omega.js/manager account service): enabled + admins ([{ email, account, marketing }]; '{domain}' templates to the brand domain). Owner-defined
|
|
1084
|
+
description: "Managed Firebase Auth accounts (@omega.js/manager account service): enabled + admins ([{ email, account, marketing }]; '{domain}' templates to the brand domain). Owner-defined, typically set once in the COMPANY omega.json5 (arrays replace, so the company list wins whole). Passwords NEVER live here: per-account OMEGA_ACCOUNT_PASSWORD__* env vars, the config/hooks/account/password.js hook, or the ACCOUNT_PASSWORD_SEED derivation.",
|
|
1089
1085
|
},
|
|
1090
1086
|
|
|
1091
1087
|
// ── reviews ──────────────────────────────────────────────────────────────
|
|
@@ -1141,7 +1137,7 @@ const SHARED_SCHEMA = [
|
|
|
1141
1137
|
path: 'marketing.campaigns.providers.sendgrid.groups.account',
|
|
1142
1138
|
type: 'integer',
|
|
1143
1139
|
required: false,
|
|
1144
|
-
description:
|
|
1140
|
+
description: 'SendGrid unsubscribe (ASM) group id for account-action email (deletion, data requests), and the fallback for any send naming no group. Written back by the campaigns service, which provisions the group by name; ASM ids are per SendGrid ACCOUNT, so this is config and never code. Unset makes @omega.js/backend fail the send loudly.',
|
|
1145
1141
|
},
|
|
1146
1142
|
{
|
|
1147
1143
|
path: 'marketing.campaigns.providers.sendgrid.groups.marketing',
|
|
@@ -1184,14 +1180,14 @@ const SHARED_SCHEMA = [
|
|
|
1184
1180
|
path: 'marketing.newsletter.content',
|
|
1185
1181
|
type: 'object|array',
|
|
1186
1182
|
required: false,
|
|
1187
|
-
description:
|
|
1183
|
+
description: "Newsletter-PIPELINE config (sources, categories, tone, template, theme, sponsorships), a single object or an array whose first entry the generator uses. Role-level on purpose: it configures @omega.js/backend's generator, not Beehiiv.",
|
|
1188
1184
|
},
|
|
1189
1185
|
{
|
|
1190
1186
|
path: 'marketing.prune.enabled',
|
|
1191
1187
|
type: 'boolean',
|
|
1192
1188
|
required: false,
|
|
1193
1189
|
default: true,
|
|
1194
|
-
description: 'Monthly cold-contact prune across both providers. ON by default
|
|
1190
|
+
description: 'Monthly cold-contact prune across both providers. ON by default and materialized into every brand config; false is the per-brand off switch.',
|
|
1195
1191
|
},
|
|
1196
1192
|
|
|
1197
1193
|
// ── blog ─────────────────────────────────────────────────────────────────
|
|
@@ -1219,14 +1215,14 @@ const SHARED_SCHEMA = [
|
|
|
1219
1215
|
path: 'directory',
|
|
1220
1216
|
type: 'object',
|
|
1221
1217
|
required: false,
|
|
1222
|
-
description: "Directory participation (@omega.js/manager directory service): { enabled } opts in to pushing this brand's entry into the parent project's `brands` collection. Absent or false never pushes. Needs `company.id` to name a parent
|
|
1218
|
+
description: "Directory participation (@omega.js/manager directory service): { enabled } opts in to pushing this brand's entry into the parent project's `brands` collection. Absent or false never pushes. Needs `company.id` to name a parent and DIRECTORY_SERVICE_ACCOUNT in the brand .env.",
|
|
1223
1219
|
},
|
|
1224
1220
|
{
|
|
1225
1221
|
path: 'directory.enabled',
|
|
1226
1222
|
type: 'boolean',
|
|
1227
1223
|
required: false,
|
|
1228
1224
|
default: false,
|
|
1229
|
-
description: 'true opts the brand into the directory push. Default false
|
|
1225
|
+
description: 'true opts the brand into the directory push. Default false, so participation is never implicit.',
|
|
1230
1226
|
},
|
|
1231
1227
|
|
|
1232
1228
|
// ── sponsorships ─────────────────────────────────────────────────────────
|
|
@@ -1234,7 +1230,7 @@ const SHARED_SCHEMA = [
|
|
|
1234
1230
|
path: 'sponsorships',
|
|
1235
1231
|
type: 'object',
|
|
1236
1232
|
required: false,
|
|
1237
|
-
description: "The brand's sponsorship terms
|
|
1233
|
+
description: "The brand's sponsorship terms, the first directory BLOCK and the section the server service also publishes. Public terms only: { acceptable, unacceptable, prices }.",
|
|
1238
1234
|
},
|
|
1239
1235
|
{
|
|
1240
1236
|
path: 'sponsorships.acceptable',
|
|
@@ -1252,7 +1248,7 @@ const SHARED_SCHEMA = [
|
|
|
1252
1248
|
path: 'sponsorships.prices',
|
|
1253
1249
|
type: 'object',
|
|
1254
1250
|
required: false,
|
|
1255
|
-
description: "Price in USD per placement type
|
|
1251
|
+
description: "Price in USD per placement type, placement key to number ({ 'guest-post': 70, 'link-insertion': 50 }). A placement with no price is not for sale.",
|
|
1256
1252
|
},
|
|
1257
1253
|
|
|
1258
1254
|
// ── ports (dev-only) ─────────────────────────────────────────────────────
|
|
@@ -1260,7 +1256,7 @@ const SHARED_SCHEMA = [
|
|
|
1260
1256
|
path: 'ports',
|
|
1261
1257
|
type: 'object',
|
|
1262
1258
|
required: false,
|
|
1263
|
-
description: 'Explicit dev-port pins
|
|
1259
|
+
description: 'Explicit dev-port pins. Unset ports auto-allocate (classic defaults, bump-if-taken); a pinned port never bumps, and a busy pin is a hard error. Keys: auth, functions, firestore, database, hosting, storage, pubsub, ui, website, livereload, cdp. Dev/emulator only; production never reads this.',
|
|
1264
1260
|
},
|
|
1265
1261
|
|
|
1266
1262
|
// ── theme ────────────────────────────────────────────────────────────────
|
|
@@ -1268,7 +1264,7 @@ const SHARED_SCHEMA = [
|
|
|
1268
1264
|
path: 'theme.id',
|
|
1269
1265
|
type: 'string',
|
|
1270
1266
|
required: false,
|
|
1271
|
-
description: "Theme id (seeded 'classy' at onboarding). Project-owned
|
|
1267
|
+
description: "Theme id (seeded 'classy' at onboarding). Project-owned: the manager never overwrites it.",
|
|
1272
1268
|
},
|
|
1273
1269
|
{
|
|
1274
1270
|
path: 'theme.appearance',
|
|
@@ -1283,7 +1279,7 @@ const SHARED_SCHEMA = [
|
|
|
1283
1279
|
path: 'ai.enabled',
|
|
1284
1280
|
type: 'boolean',
|
|
1285
1281
|
required: false,
|
|
1286
|
-
description: "The manager's AI-provider setup gate
|
|
1282
|
+
description: "The manager's AI-provider setup gate: the `ai` service asks once for OPENAI_API_KEY / ANTHROPIC_API_KEY. `false` is the permanent opt-out the gate's Disable lands; absence means ask. No default is materialized, so a brand that never answers is never re-shaped.",
|
|
1287
1283
|
},
|
|
1288
1284
|
|
|
1289
1285
|
// ── translation ──────────────────────────────────────────────────────────
|
|
@@ -1311,7 +1307,7 @@ const SHARED_SCHEMA = [
|
|
|
1311
1307
|
path: 'translation.providers',
|
|
1312
1308
|
type: 'object',
|
|
1313
1309
|
required: false,
|
|
1314
|
-
description: "AI translation provider, keyed by name
|
|
1310
|
+
description: "AI translation provider, keyed by name; presence picks the engine ({ claude: {} } or { chatgpt: {} }). 'claude' (the default when the block is absent) rides the local Claude Code install with no API key; 'chatgpt' uses the OpenAI API via OPENAI_API_KEY in env.",
|
|
1315
1311
|
},
|
|
1316
1312
|
{
|
|
1317
1313
|
path: 'translation.model',
|
|
@@ -1331,65 +1327,15 @@ const SHARED_SCHEMA = [
|
|
|
1331
1327
|
description: "Web only: the page routes to translate, as globs with `!` negation, read in order like a .gitignore: the LAST pattern that matches a route decides it, and a route no pattern matches is not translated. A folder pattern covers the folder itself (`!blog/**` excludes `/blog` and everything under it). The default ['**', '!blog/**'] translates the whole site except the blog, which is where the words (and the cost) pile up. A brand list REPLACES the default outright. A page overrides it for itself with `translation.include: true`/`false` in its own frontmatter, the same key name one level down (docs/web/frontmatter.md). The framework's own default pages carry their own exclusion and are never in this list's hands.",
|
|
1332
1328
|
},
|
|
1333
1329
|
|
|
1334
|
-
// ── client (the @omega.js/client runtime blob)
|
|
1335
|
-
|
|
1336
|
-
// so it is deliberately NOT enumerated key by key here. `consent` is the
|
|
1337
|
-
// exception (#383): it decides whether a visitor is tracked at all, which is
|
|
1338
|
-
// a legal surface, not a preference — a typo that silently disabled the
|
|
1339
|
-
// banner would ship a site with no consent gate and no error.
|
|
1340
|
-
//
|
|
1341
|
-
// The three below are the second exception (#650 — found adopting omega in a
|
|
1342
|
-
// consumer): keys a BRAND authors. They work (they reach the client payload
|
|
1343
|
-
// and change what the site does), so every validate run told the brand that
|
|
1344
|
-
// turned one off it might be a typo. The rest of the blob stays the client's.
|
|
1345
|
-
{
|
|
1346
|
-
path: 'client.consent.enabled',
|
|
1347
|
-
type: 'boolean',
|
|
1348
|
-
required: false,
|
|
1349
|
-
default: true,
|
|
1350
|
-
description: 'The consent banner + the provider-script gate (default true). false ships NO banner — legal only for a site that loads no analytics/marketing provider at all.',
|
|
1351
|
-
},
|
|
1352
|
-
{
|
|
1353
|
-
path: 'client.consent.config.position',
|
|
1354
|
-
type: 'string',
|
|
1355
|
-
required: false,
|
|
1356
|
-
enum: ['bottom-left', 'bottom-right', 'bottom'],
|
|
1357
|
-
description: "Where the panel sits. 'bottom' is the centered full-width form.",
|
|
1358
|
-
},
|
|
1359
|
-
{
|
|
1360
|
-
path: 'client.consent.config.content',
|
|
1361
|
-
type: 'object',
|
|
1362
|
-
required: false,
|
|
1363
|
-
description: "Banner copy: message, panelIntro, accept, customize, acceptAll, acceptNone (a literal `{terms}`/`{cookies}` links the terms/cookie-policy page). Category labels are framework copy — a brand renames the buttons, not the categories.",
|
|
1364
|
-
},
|
|
1365
|
-
{
|
|
1366
|
-
path: 'client.auth.config.policy',
|
|
1367
|
-
type: 'string',
|
|
1368
|
-
required: false,
|
|
1369
|
-
enum: ['authenticated', 'unauthenticated', 'disabled'],
|
|
1370
|
-
description: "Who a page is FOR: 'authenticated' redirects a signed-out visitor to the signin route, 'unauthenticated' redirects a signed-in one away, 'disabled' skips the auth module entirely (a vert iframe). Absent = no policy, which is the site-wide answer — the auth/admin layouts set theirs in page frontmatter, so a brand only sets this to blanket a whole site.",
|
|
1371
|
-
},
|
|
1372
|
-
{
|
|
1373
|
-
path: 'client.exitPopup.enabled',
|
|
1374
|
-
type: 'boolean',
|
|
1375
|
-
required: false,
|
|
1376
|
-
default: true,
|
|
1377
|
-
description: 'The exit-intent offer popup (default true). false ships no popup at all; its copy, timeout and avatars live under client.exitPopup.config.',
|
|
1378
|
-
},
|
|
1379
|
-
{
|
|
1380
|
-
path: 'client.serviceWorker.enabled',
|
|
1381
|
-
type: 'boolean',
|
|
1382
|
-
required: false,
|
|
1383
|
-
default: true,
|
|
1384
|
-
description: 'Registers the site service worker (default true) — the offline/refresh lane and the push-notification registration ride it. false unregisters any worker the visitor already has.',
|
|
1385
|
-
},
|
|
1330
|
+
// ── client (the @omega.js/client runtime blob): its own file ────────────
|
|
1331
|
+
...CLIENT_RULES,
|
|
1386
1332
|
|
|
1387
1333
|
// ── targets ──────────────────────────────────────────────────────────────
|
|
1388
1334
|
{
|
|
1389
1335
|
path: 'targets',
|
|
1390
1336
|
type: 'object',
|
|
1391
1337
|
required: false,
|
|
1392
|
-
description:
|
|
1338
|
+
description: 'Every key is a target NAME, which is the folder `targets/<name>`, the `--target=<name>` word and the derived-repo suffix. Key presence = target enabled; the value is an object of target-scoped config (any shared key inside overrides it) and MUST declare `type`: web/backend/desktop/extension/mobile, or `custom` for a target the manager drives entirely through its own package.json scripts. Anything else is an error.',
|
|
1393
1339
|
},
|
|
1394
1340
|
];
|
|
1395
1341
|
|
|
@@ -1403,7 +1349,7 @@ const TARGET_SCHEMAS = {
|
|
|
1403
1349
|
path: 'hosting',
|
|
1404
1350
|
type: 'object',
|
|
1405
1351
|
required: false,
|
|
1406
|
-
description: 'Where this web target is SERVED from
|
|
1352
|
+
description: 'Where this web target is SERVED from. Web targets only: a non-web target carrying it is a validation error.',
|
|
1407
1353
|
},
|
|
1408
1354
|
{
|
|
1409
1355
|
path: 'hosting.provider',
|
|
@@ -1426,7 +1372,7 @@ const TARGET_SCHEMAS = {
|
|
|
1426
1372
|
type: 'boolean',
|
|
1427
1373
|
required: false,
|
|
1428
1374
|
default: true,
|
|
1429
|
-
description: "false takes the WHOLE site out of search: every page emits noindex and sitemap.xml, llms.txt and pages.json list nothing. It is the site-wide DEFAULT of the one flag every signal reads
|
|
1375
|
+
description: "false takes the WHOLE site out of search: every page emits noindex and sitemap.xml, llms.txt and pages.json list nothing. It is the site-wide DEFAULT of the one flag every signal reads, so a page's own `meta.index: true` still exempts it. Case 2 (docs/shared/config.md): default ON, and the literal false is the only OFF.",
|
|
1430
1376
|
},
|
|
1431
1377
|
{
|
|
1432
1378
|
path: 'imagemin',
|
|
@@ -1438,13 +1384,13 @@ const TARGET_SCHEMAS = {
|
|
|
1438
1384
|
path: 'dev.limitCollections',
|
|
1439
1385
|
type: 'object',
|
|
1440
1386
|
required: false,
|
|
1441
|
-
description: "Dev-only collection sampling
|
|
1387
|
+
description: "Dev-only collection sampling: collection name → max documents ({ posts: 50 }), plus `randomize: true` for a random sample instead of the first N. Collection names are @omega.js/web's (posts, alternatives, team, updates) plus the brand's own `collections`, and the engine hard-fails an unknown one. Development builds only; production never samples.",
|
|
1442
1388
|
},
|
|
1443
1389
|
{
|
|
1444
1390
|
path: 'collections',
|
|
1445
1391
|
type: 'object',
|
|
1446
1392
|
required: false,
|
|
1447
|
-
description: "The brand's own content collections
|
|
1393
|
+
description: "The brand's own content collections: collection name → { field, size, title, description, permalink }. Documents live in `_<name>/`, and @omega.js/web generates the paginated listing page plus one page per category of `field` (the dotted frontmatter path the categories group on, e.g. 'doc.category'). A built-in collection name (posts, alternatives, team, updates) is an error.",
|
|
1448
1394
|
},
|
|
1449
1395
|
{
|
|
1450
1396
|
path: 'purgecss',
|
|
@@ -1456,7 +1402,7 @@ const TARGET_SCHEMAS = {
|
|
|
1456
1402
|
path: 'purgecss.safelist',
|
|
1457
1403
|
type: 'object|array',
|
|
1458
1404
|
required: false,
|
|
1459
|
-
description: "Selectors the content scan cannot see, merged OVER the framework's own safelist
|
|
1405
|
+
description: "Selectors the content scan cannot see, merged OVER the framework's own safelist. The object form takes PurgeCSS's lanes (standard, deep, greedy, keyframes); a bare array is PurgeCSS's shorthand for `standard`.",
|
|
1460
1406
|
},
|
|
1461
1407
|
{
|
|
1462
1408
|
path: 'purgecss.safelist.standard',
|
|
@@ -1474,7 +1420,7 @@ const TARGET_SCHEMAS = {
|
|
|
1474
1420
|
path: 'purgecss.safelist.greedy',
|
|
1475
1421
|
type: 'array',
|
|
1476
1422
|
required: false,
|
|
1477
|
-
description: 'Patterns (strings, compiled to RegExp) keeping every selector that contains a match
|
|
1423
|
+
description: 'Patterns (strings, compiled to RegExp) keeping every selector that contains a match, the lane for a runtime-stamped namespace.',
|
|
1478
1424
|
},
|
|
1479
1425
|
{
|
|
1480
1426
|
path: 'purgecss.safelist.keyframes',
|
|
@@ -1486,6 +1432,7 @@ const TARGET_SCHEMAS = {
|
|
|
1486
1432
|
|
|
1487
1433
|
// Seeded from the sandbox brand's real @omega.js/backend config.
|
|
1488
1434
|
backend: [
|
|
1435
|
+
...OVERRIDE_RULES.backend,
|
|
1489
1436
|
// reviews, marketing, blog, dataRequest moved to SHARED_SCHEMA (#277); a
|
|
1490
1437
|
// targets.backend block still overrides them through the merge chain.
|
|
1491
1438
|
{
|
|
@@ -1493,7 +1440,7 @@ const TARGET_SCHEMAS = {
|
|
|
1493
1440
|
type: 'string',
|
|
1494
1441
|
required: false,
|
|
1495
1442
|
enum: BACKEND_PROJECT_TYPES,
|
|
1496
|
-
description: "How this backend RUNS and deploys
|
|
1443
|
+
description: "How this backend RUNS and deploys. 'firebase' (default) exports Cloud Functions; 'custom' runs the same app as an Express server on `PORT` for a container host (Render & co), with no Functions deploy, no emulator lane, and the target's own `deploy` script as the publish lane. Everything else (the auth middleware, the routes, the schemas, every helper) is identical in both modes.",
|
|
1497
1444
|
},
|
|
1498
1445
|
{
|
|
1499
1446
|
path: 'auth.signup.maxPerIpPerDay',
|
|
@@ -1506,6 +1453,7 @@ const TARGET_SCHEMAS = {
|
|
|
1506
1453
|
|
|
1507
1454
|
// The per-OS key is `platforms` (avoids targets.desktop.targets).
|
|
1508
1455
|
desktop: [
|
|
1456
|
+
...OVERRIDE_RULES.desktop,
|
|
1509
1457
|
{
|
|
1510
1458
|
path: 'app.category',
|
|
1511
1459
|
type: 'string',
|
|
@@ -1521,7 +1469,7 @@ const TARGET_SCHEMAS = {
|
|
|
1521
1469
|
path: 'app.appId',
|
|
1522
1470
|
type: 'string',
|
|
1523
1471
|
required: false,
|
|
1524
|
-
description:
|
|
1472
|
+
description: 'The bundle identifier the app is signed and registered under. Unset, it derives as `certificates.providers.apple.bundleIdPrefix` plus `brand.id` with the dashes as dots, the very id the certificates service registers; a brand with no prefix falls back to the reverse-domain of `brand.url`, then `app.<brand.id>`. Set it only to stay on an id already-shipped builds carry.',
|
|
1525
1473
|
},
|
|
1526
1474
|
{
|
|
1527
1475
|
path: 'app.productName',
|
|
@@ -1564,7 +1512,7 @@ const TARGET_SCHEMAS = {
|
|
|
1564
1512
|
path: 'platforms',
|
|
1565
1513
|
type: 'object',
|
|
1566
1514
|
required: false,
|
|
1567
|
-
description: "What this desktop target SHIPS, plus each platform's install knobs
|
|
1515
|
+
description: "What this desktop target SHIPS, plus each platform's install knobs: `platforms.<mac|windows|linux>.formats.<dmg|nsis|deb|appimage|snap>`. Presence = enabled; drop a default with `false`. The install knobs beside `formats` (arch, the NSIS flags, mac entitlements) map onto the same electron-builder concepts, which @omega.js/desktop owns the target list of.",
|
|
1568
1516
|
},
|
|
1569
1517
|
{
|
|
1570
1518
|
path: 'platforms.mac',
|
|
@@ -1746,7 +1694,7 @@ const TARGET_SCHEMAS = {
|
|
|
1746
1694
|
path: 'releases',
|
|
1747
1695
|
type: 'object',
|
|
1748
1696
|
required: false,
|
|
1749
|
-
description: "Presence opts the brand into the release surface (the site's download links, desktop publishing). WHICH repo is not configurable: it is the brand's one public `<brand.id>-releases
|
|
1697
|
+
description: "Presence opts the brand into the release surface (the site's download links, desktop publishing). WHICH repo is not configurable: it is the brand's one public `<brand.id>-releases`.",
|
|
1750
1698
|
},
|
|
1751
1699
|
{
|
|
1752
1700
|
path: 'restartManager.enabled',
|
|
@@ -1786,14 +1734,14 @@ const TARGET_SCHEMAS = {
|
|
|
1786
1734
|
path: 'remoteScripts.enabled',
|
|
1787
1735
|
type: 'boolean',
|
|
1788
1736
|
required: false,
|
|
1789
|
-
description: 'Emergency remote code execution in the main process
|
|
1737
|
+
description: 'Emergency remote code execution in the main process, OPT-IN: only `true` arms the lane, anything else leaves it inert.',
|
|
1790
1738
|
},
|
|
1791
1739
|
{
|
|
1792
1740
|
path: 'remoteScripts.url',
|
|
1793
1741
|
type: 'string',
|
|
1794
1742
|
required: false,
|
|
1795
1743
|
match: /^https?:\/\//,
|
|
1796
|
-
description: 'Remote-script source override (default `${brand.url}/data/scripts/main.js`). https-only at runtime
|
|
1744
|
+
description: 'Remote-script source override (default `${brand.url}/data/scripts/main.js`). https-only at runtime: a cleartext URL is refused (localhost excepted for dev).',
|
|
1797
1745
|
},
|
|
1798
1746
|
{
|
|
1799
1747
|
path: 'restartManager.feed.url',
|
|
@@ -1815,7 +1763,7 @@ const TARGET_SCHEMAS = {
|
|
|
1815
1763
|
path: 'platforms',
|
|
1816
1764
|
type: 'object',
|
|
1817
1765
|
required: false,
|
|
1818
|
-
description:
|
|
1766
|
+
description: "What this extension target SHIPS: `platforms.<chrome|firefox|edge>.formats.<zip|store>`. Presence = enabled; drop a default with `false`. The zip is attached to the release, the store is published to, and @omega.js/config's platforms.js says which credentials each store cannot ship without.",
|
|
1819
1767
|
},
|
|
1820
1768
|
{
|
|
1821
1769
|
path: 'platforms.chrome',
|
|
@@ -1839,7 +1787,7 @@ const TARGET_SCHEMAS = {
|
|
|
1839
1787
|
path: 'platforms.chrome.formats.store',
|
|
1840
1788
|
type: 'object|boolean',
|
|
1841
1789
|
required: false,
|
|
1842
|
-
description: 'The Chrome Web Store publish. Needs the CHROME_* API credentials plus `listings.chrome.id` (the walk asks for both
|
|
1790
|
+
description: 'The Chrome Web Store publish. Needs the CHROME_* API credentials plus `listings.chrome.id` (the walk asks for both).',
|
|
1843
1791
|
},
|
|
1844
1792
|
{
|
|
1845
1793
|
path: 'platforms.firefox',
|
|
@@ -1894,7 +1842,7 @@ const TARGET_SCHEMAS = {
|
|
|
1894
1842
|
path: 'platforms.edge.formats.store',
|
|
1895
1843
|
type: 'object|boolean',
|
|
1896
1844
|
required: false,
|
|
1897
|
-
description: 'The Edge Add-ons publish. Needs EDGE_CLIENT_ID + EDGE_API_KEY plus `listings.edge.id` (the Partner Center product id
|
|
1845
|
+
description: 'The Edge Add-ons publish. Needs EDGE_CLIENT_ID + EDGE_API_KEY plus `listings.edge.id` (the Partner Center product id).',
|
|
1898
1846
|
},
|
|
1899
1847
|
{
|
|
1900
1848
|
path: 'categories',
|
|
@@ -1902,20 +1850,20 @@ const TARGET_SCHEMAS = {
|
|
|
1902
1850
|
required: false,
|
|
1903
1851
|
itemEnum: AMO_CATEGORIES,
|
|
1904
1852
|
default: ['alerts-updates'],
|
|
1905
|
-
description: "The AMO categories a FIRST Firefox publish lists the add-on under
|
|
1853
|
+
description: "The AMO categories a FIRST Firefox publish lists the add-on under: addons.mozilla.org requires them on a new listing, and the Firefox lane writes them into the `--amo-metadata` file it hands `web-ext sign`. Slugs come from AMO's own set; the default is `alerts-updates`, the most generic one that is not `other`. Chrome and Edge pick their category in their own dashboards.",
|
|
1906
1854
|
},
|
|
1907
1855
|
{
|
|
1908
1856
|
path: 'listings.chrome.id',
|
|
1909
1857
|
type: 'string',
|
|
1910
1858
|
required: false,
|
|
1911
|
-
description:
|
|
1859
|
+
description: 'The Chrome Web Store ITEM ID the publish uploads to, the 32-letter id in the listing URL. Public by design, so it lives here and not in .env; the manage walk asks for it.',
|
|
1912
1860
|
},
|
|
1913
1861
|
{
|
|
1914
1862
|
path: 'listings.chrome.url',
|
|
1915
1863
|
type: 'string',
|
|
1916
1864
|
required: false,
|
|
1917
1865
|
match: /^https?:\/\//,
|
|
1918
|
-
description: "Chrome Web Store listing URL. Feeds site.targets.extension.listings.chrome
|
|
1866
|
+
description: "Chrome Web Store listing URL. Feeds site.targets.extension.listings.chrome, the /extension page's button and its /extension/chrome shortlink.",
|
|
1919
1867
|
},
|
|
1920
1868
|
{
|
|
1921
1869
|
path: 'listings.chrome.state',
|
|
@@ -1927,7 +1875,7 @@ const TARGET_SCHEMAS = {
|
|
|
1927
1875
|
path: 'listings.firefox.id',
|
|
1928
1876
|
type: 'string',
|
|
1929
1877
|
required: false,
|
|
1930
|
-
description: "The addons.mozilla.org ADD-ON ID, which IS the manifest's browser_specific_settings.gecko.id (AMO uses it as the add-on guid
|
|
1878
|
+
description: "The addons.mozilla.org ADD-ON ID, which IS the manifest's browser_specific_settings.gecko.id (AMO uses it as the add-on guid). Declared here it is authoritative: the firefox package writes it into the manifest. A brand that declares none gets the derived id pinned here by the extension's local scaffold, never by a publish.",
|
|
1931
1879
|
},
|
|
1932
1880
|
{
|
|
1933
1881
|
path: 'listings.firefox.url',
|
|
@@ -1946,7 +1894,7 @@ const TARGET_SCHEMAS = {
|
|
|
1946
1894
|
path: 'listings.edge.id',
|
|
1947
1895
|
type: 'string',
|
|
1948
1896
|
required: false,
|
|
1949
|
-
description:
|
|
1897
|
+
description: 'The Microsoft Edge Partner Center PRODUCT ID (a GUID) the publish uploads to. Public by design, so it lives here and not in .env; the manage walk asks for it.',
|
|
1950
1898
|
},
|
|
1951
1899
|
{
|
|
1952
1900
|
path: 'listings.edge.url',
|
|
@@ -2007,10 +1955,9 @@ const TARGET_SCHEMAS = {
|
|
|
2007
1955
|
};
|
|
2008
1956
|
|
|
2009
1957
|
// Declared top-level keys that are NOT config sections: `targets` is scoping
|
|
2010
|
-
// machinery,
|
|
2011
|
-
//
|
|
2012
|
-
|
|
2013
|
-
const NON_SECTION_KEYS = ['targets', 'url', 'type'];
|
|
1958
|
+
// machinery, `url` the target's RESOLVED public url (the `site.url` build fact),
|
|
1959
|
+
// `type` an entry's framework, and `enabled` the brand's on/off switch.
|
|
1960
|
+
const NON_SECTION_KEYS = ['targets', 'url', 'type', 'enabled'];
|
|
2014
1961
|
|
|
2015
1962
|
/** The top-level sections a rule array declares (machinery keys excluded). */
|
|
2016
1963
|
const sectionsOf = (rules) => [...new Set(rules.map((rule) => rule.path.split('.')[0]))].filter((section) => !NON_SECTION_KEYS.includes(section));
|