@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The pipeline shared by the `build`, `package`, `publish` and `dev` verbs.
|
|
2
2
|
//
|
|
3
3
|
// The CLI verbs are canonical and the synced projectScripts are thin
|
|
4
4
|
// `omega <verb>` aliases, so a verb must run the pipeline ITSELF and never
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// Libraries
|
|
11
11
|
const { execute } = require('node-powertools');
|
|
12
12
|
|
|
13
|
-
function gulpCommand(task) {
|
|
14
|
-
return `npm run gulp -- ${task}`;
|
|
13
|
+
function gulpCommand(task, args = []) {
|
|
14
|
+
return `npm run gulp -- ${[task, ...args].join(' ')}`;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Default step implementations — required lazily so requiring a plan never
|
|
@@ -30,7 +30,7 @@ const RUNNERS = {
|
|
|
30
30
|
'ship-keys': () => require('./ship-keys.js').assertShipKeys(),
|
|
31
31
|
// `strict` rides the STEP: publish declares it (#891), a bare verb does not.
|
|
32
32
|
'validate-certs': (options, step) => require('../commands/validate-certs.js')({ ...options, strict: step.strict === true || options.strict === true }),
|
|
33
|
-
gulp: (options, step) => execute(gulpCommand(step.task), { log: true }),
|
|
33
|
+
gulp: (options, step) => execute(gulpCommand(step.task, step.args), { log: true }),
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
async function runPipeline(plan, options, runners) {
|
package/dist/utils/runner-env.js
CHANGED
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
const fs = require('fs');
|
|
36
36
|
const os = require('os');
|
|
37
37
|
const path = require('path');
|
|
38
|
+
const { envLine, assertEnvReadsBack } = require('../vendor/config/index.js');
|
|
39
|
+
const { setEnvLines } = require('../vendor/devkit/env-lines.js');
|
|
38
40
|
|
|
39
41
|
// Runner files live under %LOCALAPPDATA%\omega-runner — a per-user path that
|
|
40
42
|
// doesn't need admin to read/write. Set OMEGA_RUNNER_HOME to override.
|
|
@@ -308,37 +310,20 @@ function ensureRunnerEnvFile(home) {
|
|
|
308
310
|
return true;
|
|
309
311
|
}
|
|
310
312
|
|
|
311
|
-
// Write values into the file IN PLACE
|
|
312
|
-
//
|
|
313
|
-
//
|
|
313
|
+
// Write values into the file IN PLACE through devkit's setEnvLines: a key's
|
|
314
|
+
// first line or `# KEY=""` placeholder takes the new line and later ones drop,
|
|
315
|
+
// so dotenv reads the new value; a key the file never had is appended. Comments
|
|
316
|
+
// and order survive, so the file stays the one a person reads.
|
|
314
317
|
function writeRunnerEnvValues(home, values) {
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
// all: refuse it — naming the key and the character — before the file is
|
|
319
|
-
// touched, rather than save a value that reads back wrong.
|
|
320
|
-
const serialize = (key, value) => `${key}="${value}"`;
|
|
321
|
-
for (const [key, value] of Object.entries(values)) {
|
|
322
|
-
if (String(value).includes('"')) {
|
|
323
|
-
throw new Error(`${key} contains a double quote (") — a .env value cannot carry one. Remove it, then set ${key} again.`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
318
|
+
// envLine is the one .env serializer; it refuses, naming the key, a value
|
|
319
|
+
// dotenv would read back changed, before the file is touched.
|
|
320
|
+
const lineFor = Object.fromEntries(Object.entries(values).map(([key, value]) => [key, envLine(key, value)]));
|
|
326
321
|
|
|
327
322
|
ensureRunnerEnvFile(home);
|
|
328
|
-
const file
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const out = lines.map((line) => {
|
|
333
|
-
const m = /^\s*#?\s*([A-Z][A-Z0-9_]*)\s*=/.exec(line);
|
|
334
|
-
if (!m || !(m[1] in values) || seen.has(m[1])) return line;
|
|
335
|
-
seen.add(m[1]);
|
|
336
|
-
return serialize(m[1], String(values[m[1]]));
|
|
337
|
-
});
|
|
338
|
-
for (const [key, value] of Object.entries(values)) {
|
|
339
|
-
if (!seen.has(key)) out.push(serialize(key, String(value)));
|
|
340
|
-
}
|
|
341
|
-
fs.writeFileSync(file, out.join('\n').replace(/\n*$/, '\n'));
|
|
323
|
+
const file = runnerEnvFile(home);
|
|
324
|
+
const content = setEnvLines(fs.readFileSync(file, 'utf8'), lineFor);
|
|
325
|
+
assertEnvReadsBack(content, values);
|
|
326
|
+
fs.writeFileSync(file, content);
|
|
342
327
|
return file;
|
|
343
328
|
}
|
|
344
329
|
|
|
@@ -44,6 +44,9 @@ const fs = require('node:fs');
|
|
|
44
44
|
const os = require('node:os');
|
|
45
45
|
const path = require('node:path');
|
|
46
46
|
const JSON5 = require('json5');
|
|
47
|
+
const Logger = require('../devkit/logger.js');
|
|
48
|
+
|
|
49
|
+
const logger = new Logger('company');
|
|
47
50
|
|
|
48
51
|
// The company TREE inside the parent brand's repo, and the file an off-laptop
|
|
49
52
|
// run reads the resolved layer from: GENERATED beside the brand config by the
|
|
@@ -62,13 +65,16 @@ const REGISTRY_FILE = 'brands.json';
|
|
|
62
65
|
// it visible").
|
|
63
66
|
const SELF = 'self';
|
|
64
67
|
|
|
65
|
-
// The
|
|
66
|
-
const
|
|
68
|
+
// The `company` keys the LOADER fills: never typed, always resolved.
|
|
69
|
+
const RESOLVED_COMPANY_KEYS = ['name', 'url', 'images'];
|
|
67
70
|
|
|
68
71
|
// One warning per company id per process: a missing parent is a state a whole
|
|
69
72
|
// build runs in, never a per-call event.
|
|
70
73
|
const warned = new Set();
|
|
71
74
|
|
|
75
|
+
// One warning per process for a registry file that is there but will not parse.
|
|
76
|
+
let warnedUnreadable = false;
|
|
77
|
+
|
|
72
78
|
/**
|
|
73
79
|
* The machine home OMEGA keeps its per-machine state in. `OMEGA_HOME` moves it
|
|
74
80
|
* (the test lanes point it at a temp dir so a fixture never writes a real line).
|
|
@@ -118,6 +124,17 @@ function readRegistry() {
|
|
|
118
124
|
return readJson(registryFile()) || {};
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
/**
|
|
128
|
+
* A registry line's root when it is on this machine now, else null: the ONE
|
|
129
|
+
* test both the reader and the write's prune apply, so a line the reader would
|
|
130
|
+
* resolve is never a line the prune drops.
|
|
131
|
+
* @param {object} line - A registry line.
|
|
132
|
+
* @returns {string|null}
|
|
133
|
+
*/
|
|
134
|
+
function liveRoot(line) {
|
|
135
|
+
return line && typeof line.root === 'string' && isDir(line.root) ? line.root : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
121
138
|
/**
|
|
122
139
|
* Record (or refresh) ONE brand's line in the machine registry, which is what
|
|
123
140
|
* every `loadConfig()` does for the brand it just loaded: the map of where the
|
|
@@ -130,7 +147,13 @@ function readRegistry() {
|
|
|
130
147
|
*
|
|
131
148
|
* A write also PRUNES the lines whose root is gone: the file is a map of what
|
|
132
149
|
* is on this machine, so a brand that was deleted or moved (and re-recorded
|
|
133
|
-
* under its new root) leaves nothing behind to resolve into.
|
|
150
|
+
* under its new root) leaves nothing behind to resolve into. A line whose root
|
|
151
|
+
* is on disk is never pruned (`liveRoot`, the reader's own test).
|
|
152
|
+
*
|
|
153
|
+
* A registry file that is THERE but will not parse is never rewritten: writing
|
|
154
|
+
* over it would keep one line and drop every other brand. The write itself is
|
|
155
|
+
* a rename of a finished temp file, so a racing reader never sees half a file.
|
|
156
|
+
* Every write logs its caller and the kept and pruned ids at debug level.
|
|
134
157
|
*
|
|
135
158
|
* @param {object} entry - The brand's own facts.
|
|
136
159
|
* @param {string} entry.id - `brand.id` (the key).
|
|
@@ -143,7 +166,17 @@ function recordBrand({ id, root, name, url }) {
|
|
|
143
166
|
if (!id || !root) return false;
|
|
144
167
|
|
|
145
168
|
try {
|
|
146
|
-
const
|
|
169
|
+
const file = registryFile();
|
|
170
|
+
const registry = fs.existsSync(file) ? readJson(file) : {};
|
|
171
|
+
|
|
172
|
+
if (!registry) {
|
|
173
|
+
if (!warnedUnreadable) {
|
|
174
|
+
warnedUnreadable = true;
|
|
175
|
+
logger.warn(`Machine registry ${file} does not parse: left as it is, no brand recorded. Delete it and any omega verb rebuilds it.`);
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
147
180
|
const current = registry[id];
|
|
148
181
|
const line = { root, name: name || null, url: url || null };
|
|
149
182
|
|
|
@@ -153,10 +186,15 @@ function recordBrand({ id, root, name, url }) {
|
|
|
153
186
|
|
|
154
187
|
registry[id] = { ...line, updatedAt: new Date().toISOString() };
|
|
155
188
|
|
|
156
|
-
const live = Object.fromEntries(Object.entries(registry).filter(([, entry]) =>
|
|
189
|
+
const live = Object.fromEntries(Object.entries(registry).filter(([, entry]) => liveRoot(entry)));
|
|
190
|
+
const pruned = Object.keys(registry).filter((key) => !(key in live));
|
|
157
191
|
|
|
158
192
|
fs.mkdirSync(omegaHome(), { recursive: true });
|
|
159
|
-
|
|
193
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
194
|
+
fs.writeFileSync(temp, `${JSON.stringify(live, null, 2)}\n`);
|
|
195
|
+
fs.renameSync(temp, file);
|
|
196
|
+
|
|
197
|
+
logger.debug(`Registry write by ${process.argv[1] || process.argv0} (pid ${process.pid}): kept [${Object.keys(live).join(', ')}], pruned [${pruned.join(', ')}]`);
|
|
160
198
|
return true;
|
|
161
199
|
} catch {
|
|
162
200
|
return false;
|
|
@@ -271,13 +309,6 @@ function resolveCompany(brandRoot, brandConfig) {
|
|
|
271
309
|
const webhooks = !(config.company && config.company.webhooks === false);
|
|
272
310
|
const reply = (facts) => answer({ ...facts, webhooks });
|
|
273
311
|
|
|
274
|
-
// A brand still carrying the retired stamp hears about it once: the folder it
|
|
275
|
-
// points at is not read any more, by anything.
|
|
276
|
-
if (root && fs.existsSync(path.join(root, RETIRED_MARKER)) && !warned.has(RETIRED_MARKER)) {
|
|
277
|
-
warned.add(RETIRED_MARKER);
|
|
278
|
-
console.warn(`${path.join(root, RETIRED_MARKER)} is retired (#677): delete it, and name the company with company: { id: '<parent brand.id>' } in omega.json5.`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
312
|
// No company: the brand IS the whole entity, so the company facts are its own
|
|
282
313
|
// and every reader works without a fallback.
|
|
283
314
|
if (!id) {
|
|
@@ -290,7 +321,7 @@ function resolveCompany(brandRoot, brandConfig) {
|
|
|
290
321
|
}
|
|
291
322
|
|
|
292
323
|
const line = readRegistry()[id];
|
|
293
|
-
const parentRoot =
|
|
324
|
+
const parentRoot = liveRoot(line);
|
|
294
325
|
|
|
295
326
|
if (parentRoot) {
|
|
296
327
|
const parentBrand = (readBrandConfig(parentRoot) || {}).brand || {};
|
|
@@ -347,4 +378,5 @@ module.exports = {
|
|
|
347
378
|
COMPANY_DIR,
|
|
348
379
|
COMPANY_RESOLVED_FILE,
|
|
349
380
|
COMPANY_SELF: SELF,
|
|
381
|
+
RESOLVED_COMPANY_KEYS,
|
|
350
382
|
};
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* brand config is a copy that drifts from the thing it came from.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
31
|
+
|
|
30
32
|
const { SHARED_SCHEMA, TARGET_SCHEMAS } = require('./schema.js');
|
|
31
33
|
const { isPlainObject } = require('./merge.js');
|
|
32
34
|
|
|
@@ -59,6 +61,12 @@ function defaultRules(target) {
|
|
|
59
61
|
return rules.filter(hasDefault);
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Set a value at a dot-path, creating the objects on the way.
|
|
66
|
+
* @param {object} target
|
|
67
|
+
* @param {string} dotted
|
|
68
|
+
* @param {*} value
|
|
69
|
+
*/
|
|
62
70
|
function setAtPath(target, dotted, value) {
|
|
63
71
|
const names = dotted.split('.');
|
|
64
72
|
const leaf = names.pop();
|
|
@@ -127,13 +135,26 @@ function defaultComments(target) {
|
|
|
127
135
|
return comments;
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
/**
|
|
139
|
+
* What filling `present` from `incoming` does, key by key, never overwriting:
|
|
140
|
+
* a path `present` lacks is ADDED at its highest missing point (one block,
|
|
141
|
+
* not one edit per key inside it), and a path `present` already answers
|
|
142
|
+
* differently is KEPT. An equal value is neither.
|
|
143
|
+
* @param {object} incoming - The values on offer.
|
|
144
|
+
* @param {object} present - The authored config they would fill.
|
|
145
|
+
* @returns {{ added: Array<{ path: string, value: * }>, kept: Array<{ path: string, value: *, incoming: * }> }}
|
|
146
|
+
*/
|
|
147
|
+
function planMerge(incoming, present) {
|
|
148
|
+
return collectPlan(incoming, present, '', { added: [], kept: [] });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function collectPlan(incoming, present, prefix, plan) {
|
|
152
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
132
153
|
const dotted = prefix ? `${prefix}.${key}` : key;
|
|
133
154
|
const authored = isPlainObject(present) && Object.prototype.hasOwnProperty.call(present, key);
|
|
134
155
|
|
|
135
156
|
if (!authored) {
|
|
136
|
-
|
|
157
|
+
plan.added.push({ path: dotted, value });
|
|
137
158
|
continue;
|
|
138
159
|
}
|
|
139
160
|
|
|
@@ -141,11 +162,13 @@ function collectMissing(defaults, present, prefix, target, found) {
|
|
|
141
162
|
// A brand that authored anything else at this path (a scalar, false, null)
|
|
142
163
|
// made a decision: never dive in, never overwrite.
|
|
143
164
|
if (isPlainObject(value) && isPlainObject(present[key])) {
|
|
144
|
-
|
|
165
|
+
collectPlan(value, present[key], dotted, plan);
|
|
166
|
+
} else if (!isDeepStrictEqual(value, present[key])) {
|
|
167
|
+
plan.kept.push({ path: dotted, value: present[key], incoming: value });
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
|
|
148
|
-
return
|
|
171
|
+
return plan;
|
|
149
172
|
}
|
|
150
173
|
|
|
151
174
|
/**
|
|
@@ -167,7 +190,7 @@ function missingDefaults(config, target) {
|
|
|
167
190
|
// copy to drift ([#793](https://github.com/Omega-JS-Stack/omega/issues/793))
|
|
168
191
|
const writable = defaultsFrom(defaultRules(target).filter(isMaterialized));
|
|
169
192
|
|
|
170
|
-
return
|
|
193
|
+
return planMerge(writable, config || {}).added;
|
|
171
194
|
}
|
|
172
195
|
|
|
173
|
-
module.exports = { schemaDefaults, missingDefaults, defaultComments, defaultRules };
|
|
196
|
+
module.exports = { schemaDefaults, missingDefaults, defaultComments, defaultRules, planMerge, setAtPath };
|
|
@@ -579,12 +579,23 @@ function applyConfigEdits(source, edits, { comments = {} } = {}) {
|
|
|
579
579
|
* @param {{ dryRun?: boolean, comments?: Object<string, string> }} [options]
|
|
580
580
|
* @returns {{ path: string, changed: boolean, applied: string[] }}
|
|
581
581
|
*/
|
|
582
|
-
function writeConfigValues(projectDir, edits,
|
|
582
|
+
function writeConfigValues(projectDir, edits, options) {
|
|
583
583
|
const configPath = resolveConfigPath(projectDir);
|
|
584
584
|
if (!configPath) {
|
|
585
585
|
throw new Error(`No ${FILE_NAME} found under ${projectDir} — cannot write config values`);
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
+
return writeConfigFileValues(configPath, edits, options);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* writeConfigValues on one named omega file (an overlay, a target's own file).
|
|
593
|
+
* @param {string} configPath - The file to edit.
|
|
594
|
+
* @param {Object<string, *>} edits - Dot-path → value.
|
|
595
|
+
* @param {{ dryRun?: boolean, comments?: Object<string, string> }} [options]
|
|
596
|
+
* @returns {{ path: string, changed: boolean, applied: string[] }}
|
|
597
|
+
*/
|
|
598
|
+
function writeConfigFileValues(configPath, edits, { dryRun = false, comments = {} } = {}) {
|
|
588
599
|
const source = fs.readFileSync(configPath, 'utf8');
|
|
589
600
|
const applied = pendingEdits(JSON5.parse(source), edits).map(([path]) => path);
|
|
590
601
|
// Every writeback also normalizes top-level key order (comments travel
|
|
@@ -748,12 +759,23 @@ function applyConfigRemovals(source, paths) {
|
|
|
748
759
|
* @param {{ dryRun?: boolean }} [options]
|
|
749
760
|
* @returns {{ path: string, changed: boolean, removed: string[] }}
|
|
750
761
|
*/
|
|
751
|
-
function removeConfigValues(projectDir, paths,
|
|
762
|
+
function removeConfigValues(projectDir, paths, options) {
|
|
752
763
|
const configPath = resolveConfigPath(projectDir);
|
|
753
764
|
if (!configPath) {
|
|
754
765
|
throw new Error(`No ${FILE_NAME} found under ${projectDir} — cannot remove config values`);
|
|
755
766
|
}
|
|
756
767
|
|
|
768
|
+
return removeConfigFileValues(configPath, paths, options);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* removeConfigValues on one named omega file (an overlay, a target's own file).
|
|
773
|
+
* @param {string} configPath - The file to edit.
|
|
774
|
+
* @param {string[]} paths - Dot-paths to delete.
|
|
775
|
+
* @param {{ dryRun?: boolean }} [options]
|
|
776
|
+
* @returns {{ path: string, changed: boolean, removed: string[] }}
|
|
777
|
+
*/
|
|
778
|
+
function removeConfigFileValues(configPath, paths, { dryRun = false } = {}) {
|
|
757
779
|
const source = fs.readFileSync(configPath, 'utf8');
|
|
758
780
|
const parsed = JSON5.parse(source);
|
|
759
781
|
const removed = paths.filter((path) => getAtPath(parsed, path) !== undefined);
|
|
@@ -766,4 +788,4 @@ function removeConfigValues(projectDir, paths, { dryRun = false } = {}) {
|
|
|
766
788
|
return { path: configPath, changed: next !== source, removed };
|
|
767
789
|
}
|
|
768
790
|
|
|
769
|
-
module.exports = { applyConfigEdits, writeConfigValues, applyConfigRemovals, removeConfigValues, parseRoot };
|
|
791
|
+
module.exports = { applyConfigEdits, writeConfigValues, writeConfigFileValues, applyConfigRemovals, removeConfigValues, removeConfigFileValues, parseRoot };
|
|
@@ -54,7 +54,7 @@ const WORKFLOW_OWNED_KEYS = ['GH_TOKEN', 'CLOUDFLARE_TOKEN', 'NODE_VERSION', 'NO
|
|
|
54
54
|
|
|
55
55
|
// Rendered in place of the block when a target delivers nothing, so the
|
|
56
56
|
// generated region is always a valid, self-explaining line of YAML.
|
|
57
|
-
const EMPTY_BLOCK = '# (no CI-delivered keys for this target
|
|
57
|
+
const EMPTY_BLOCK = '# (no CI-delivered keys for this target: the next omega verb regenerates this block)';
|
|
58
58
|
|
|
59
59
|
// What a target's generated workflow carries in the runner env, per target.
|
|
60
60
|
//
|
|
@@ -874,12 +874,9 @@ const ENV_SCHEMA = [
|
|
|
874
874
|
description: 'Edge Add-ons API key the publish request authenticates with.',
|
|
875
875
|
},
|
|
876
876
|
|
|
877
|
-
// No test-lane credentials live here
|
|
878
|
-
//
|
|
879
|
-
//
|
|
880
|
-
// against a persona the backend emulator seeds, so a suite never asks a
|
|
881
|
-
// brand for a key. The pair that used to sit here is retired outright
|
|
882
|
-
// (env-retired.js carries both rows); #904 owns the replacement.
|
|
877
|
+
// No test-lane credentials live here: web, desktop and extension each sign
|
|
878
|
+
// in as a persona the backend emulator seeds, so a suite never asks a brand
|
|
879
|
+
// for a key. The retired pair's rows are @omega.js/manager's migrate table.
|
|
883
880
|
|
|
884
881
|
{
|
|
885
882
|
name: 'OMEGA_FONTAWESOME_ROOT',
|
|
@@ -32,7 +32,6 @@ const path = require('node:path');
|
|
|
32
32
|
const { findBrandRoot } = require('./load.js');
|
|
33
33
|
const { resolveCompany } = require('./company.js');
|
|
34
34
|
const { ENV_SCHEMA, envFileGroups, envSchemaEntry } = require('./env-schema.js');
|
|
35
|
-
const { assertNoRetiredEnvKeys } = require('./env-retired.js');
|
|
36
35
|
// The one vocabulary lives with the one environment module (#817); this file
|
|
37
36
|
// re-exports it so the .env overlay names and the runtime answer stay one list.
|
|
38
37
|
const { ENV_ENVIRONMENTS } = require('./environment.js');
|
|
@@ -322,11 +321,8 @@ function reloadEnv(startDir, { target, environment = envEnvironment() } = {}) {
|
|
|
322
321
|
/**
|
|
323
322
|
* Parse one .env file into a plain map. Missing files read as empty.
|
|
324
323
|
*
|
|
325
|
-
* The
|
|
326
|
-
*
|
|
327
|
-
* is no dual-read, so a line for a key that moved into config is a value
|
|
328
|
-
* nothing consults, and both readers below (the process cascade and the
|
|
329
|
-
* artifact composer) fail on it naming the move.
|
|
324
|
+
* The layer is OPEN: an .env may carry keys outside omega, so a key nothing
|
|
325
|
+
* reads (a retired one included) is simply unread. `omega migrate` names those.
|
|
330
326
|
*
|
|
331
327
|
* @param {string|null} envPath
|
|
332
328
|
* @returns {Object<string, string>} Parsed key → value.
|
|
@@ -334,10 +330,7 @@ function reloadEnv(startDir, { target, environment = envEnvironment() } = {}) {
|
|
|
334
330
|
function parseEnvFile(envPath) {
|
|
335
331
|
if (!envPath || !fs.existsSync(envPath)) return {};
|
|
336
332
|
|
|
337
|
-
|
|
338
|
-
assertNoRetiredEnvKeys(parsed, envPath);
|
|
339
|
-
|
|
340
|
-
return parsed;
|
|
333
|
+
return require('dotenv').parse(fs.readFileSync(envPath, 'utf8'));
|
|
341
334
|
}
|
|
342
335
|
|
|
343
336
|
/**
|
|
@@ -441,34 +434,53 @@ function composeTargetEnv({ targetDir, target, environment = envEnvironment() })
|
|
|
441
434
|
}
|
|
442
435
|
|
|
443
436
|
/**
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
437
|
+
* Throw unless dotenv reads every given value back from `content` exactly as
|
|
438
|
+
* given. The error names the keys, never a value.
|
|
439
|
+
*
|
|
440
|
+
* @param {string} content - .env content holding the written lines.
|
|
441
|
+
* @param {Object<string, string>} values - The values those lines must carry.
|
|
442
|
+
*/
|
|
443
|
+
function assertEnvReadsBack(content, values) {
|
|
444
|
+
const parsed = require('dotenv').parse(content);
|
|
445
|
+
const changed = Object.keys(values).filter((key) => parsed[key] !== String(values[key]));
|
|
446
|
+
if (changed.length === 0) return;
|
|
447
|
+
|
|
448
|
+
throw new Error(`.env: the value of ${changed.join(', ')} would not read back unchanged from KEY="value" (dotenv expands a literal \\n or \\r, a quote followed by # ends the value, and a trailing backslash can run into the next line)`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Serialize one value as a double-quoted .env line, the serializer SSOT every
|
|
453
|
+
* writeback rides. The value goes in raw (dotenv reads `"`, `\`, `'`, `#` back
|
|
454
|
+
* unchanged); a real newline or carriage return becomes `\n` / `\r`, which
|
|
455
|
+
* dotenv expands back. A value dotenv would read back changed, such as a
|
|
456
|
+
* literal backslash-n or backslash-r, throws naming the key only.
|
|
448
457
|
*
|
|
449
458
|
* @param {string} key - Env var name.
|
|
450
459
|
* @param {string} value - Value to serialize.
|
|
451
460
|
* @returns {string} `KEY="value"`.
|
|
452
461
|
*/
|
|
453
462
|
function envLine(key, value) {
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.replace(/\n/g, '\\n');
|
|
463
|
+
const raw = String(value).replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
464
|
+
const line = `${key}="${raw}"`;
|
|
465
|
+
assertEnvReadsBack(line, { [key]: value });
|
|
458
466
|
|
|
459
|
-
return
|
|
467
|
+
return line;
|
|
460
468
|
}
|
|
461
469
|
|
|
462
470
|
/**
|
|
463
|
-
* Serialize composed values as .env content
|
|
471
|
+
* Serialize composed values as .env content, one envLine per key. The whole
|
|
472
|
+
* file is judged too: a value ending in a backslash can carry dotenv's read
|
|
473
|
+
* into the next line, which no single line shows.
|
|
464
474
|
*
|
|
465
475
|
* @param {Object<string, string>} values
|
|
466
476
|
* @returns {string} The file content, newline-terminated.
|
|
467
477
|
*/
|
|
468
478
|
function serializeEnv(values) {
|
|
469
479
|
const lines = Object.entries(values).map(([key, value]) => envLine(key, value));
|
|
480
|
+
const content = `${lines.join('\n')}\n`;
|
|
481
|
+
assertEnvReadsBack(content, values);
|
|
470
482
|
|
|
471
|
-
return
|
|
483
|
+
return content;
|
|
472
484
|
}
|
|
473
485
|
|
|
474
|
-
module.exports = { loadEnv, reloadEnv, ENV_ENVIRONMENTS, envEnvironment, resolveEnvChain, envLayerFiles, loadEnvChain, loadEnvRoots, applyDeliverAs, composeTargetEnv, envLine, serializeEnv };
|
|
486
|
+
module.exports = { loadEnv, reloadEnv, ENV_ENVIRONMENTS, envEnvironment, resolveEnvChain, envLayerFiles, loadEnvChain, loadEnvRoots, applyDeliverAs, composeTargetEnv, assertEnvReadsBack, envLine, serializeEnv };
|
|
@@ -23,19 +23,17 @@ const { TARGETS, CUSTOM_TARGET_TYPE, isCustomTargetEntry, BACKEND_PROJECT_TYPES,
|
|
|
23
23
|
const { clientConfig, CLIENT_FACT_KEYS } = require('./client-config.js');
|
|
24
24
|
const { deepMerge } = require('./merge.js');
|
|
25
25
|
const { findSecretKeys, SECRET_KEY_PATTERN } = require('./secrets.js');
|
|
26
|
-
const { findRetiredKeys, RETIRED_KEYS, RETIRED_PATHS } = require('./retired-keys.js');
|
|
27
26
|
const { chosenProvider } = require('./providers.js');
|
|
28
|
-
const { validateConfig, runSchema, formatErrors, resolvedBrandHost } = require('./validate.js');
|
|
29
|
-
const { loadConfig, composeTargetConfig, hasOmegaConfig, resolveConfigPath, getEnabledTargets, findBrandRoot, findBrandConfigPath, resolveBrandRoot, FILE_NAME, CONFIG_LOCATIONS } = require('./load.js');
|
|
27
|
+
const { validateConfig, undeclaredPaths, undeclaredAuthoredPaths, runSchema, formatErrors, resolvedBrandHost } = require('./validate.js');
|
|
28
|
+
const { loadConfig, composeTargetConfig, hasOmegaConfig, resolveConfigPath, overlayPath, getEnabledTargets, findBrandRoot, findBrandConfigPath, resolveBrandRoot, FILE_NAME, CONFIG_LOCATIONS } = require('./load.js');
|
|
30
29
|
const { ENV_ENVIRONMENTS, ENVIRONMENT_VAR, getEnvironment, isDevelopment, isProduction, isTesting, setEnvironment, buildLaneEnvironment } = require('./environment.js');
|
|
31
|
-
const { loadEnv, reloadEnv, envEnvironment, resolveEnvChain, envLayerFiles, loadEnvChain, loadEnvRoots, applyDeliverAs, composeTargetEnv, envLine, serializeEnv } = require('./env.js');
|
|
30
|
+
const { loadEnv, reloadEnv, envEnvironment, resolveEnvChain, envLayerFiles, loadEnvChain, loadEnvRoots, applyDeliverAs, composeTargetEnv, assertEnvReadsBack, envLine, serializeEnv } = require('./env.js');
|
|
32
31
|
const { ENV_SCHEMA, ENV_GROUPS, DELIVERY_MODES, envFileGroups, envSchemaEntry, envKeysForTarget, generatedEnvKeys, requiredEnvKeys, envKeysByGroup } = require('./env-schema.js');
|
|
33
32
|
const { WORKFLOW_OWNED_KEYS, deliveredKeys, workflowSecretKeys, envFileKeys, artifactEnvValues, bakeKeys, bakeSourceKeys, publishSecretKeys, renderSecretsBlock, renderEnvFileKeys } = require('./env-delivery.js');
|
|
34
33
|
const { checkEnvRules } = require('./env-rules.js');
|
|
35
|
-
const { RETIRED_ENV_KEYS, findRetiredEnvKeys, assertNoRetiredEnvKeys } = require('./env-retired.js');
|
|
36
34
|
const { resolveCompany, recordBrand, readRegistry, registryFile, COMPANY_DIR, COMPANY_RESOLVED_FILE, COMPANY_SELF } = require('./company.js');
|
|
37
|
-
const { applyConfigEdits, writeConfigValues, applyConfigRemovals, removeConfigValues } = require('./edit.js');
|
|
38
|
-
const { schemaDefaults, missingDefaults, defaultComments } = require('./defaults.js');
|
|
35
|
+
const { applyConfigEdits, writeConfigValues, writeConfigFileValues, applyConfigRemovals, removeConfigValues, removeConfigFileValues } = require('./edit.js');
|
|
36
|
+
const { schemaDefaults, missingDefaults, defaultComments, planMerge, setAtPath } = require('./defaults.js');
|
|
39
37
|
const { applyCanonicalOrder, CANONICAL_TOP_LEVEL_ORDER } = require('./order.js');
|
|
40
38
|
const { resolveSeedMode } = require('./seed.js');
|
|
41
39
|
const { resolveHook, loadHook } = require('./hooks.js');
|
|
@@ -55,6 +53,7 @@ module.exports = {
|
|
|
55
53
|
composeTargetConfig,
|
|
56
54
|
hasOmegaConfig,
|
|
57
55
|
resolveConfigPath,
|
|
56
|
+
overlayPath,
|
|
58
57
|
getEnabledTargets,
|
|
59
58
|
findBrandRoot,
|
|
60
59
|
findBrandConfigPath,
|
|
@@ -101,6 +100,7 @@ module.exports = {
|
|
|
101
100
|
composeTargetEnv,
|
|
102
101
|
|
|
103
102
|
// The .env serializer SSOT — every writeback renders through these
|
|
103
|
+
assertEnvReadsBack,
|
|
104
104
|
envLine,
|
|
105
105
|
serializeEnv,
|
|
106
106
|
|
|
@@ -136,13 +136,6 @@ module.exports = {
|
|
|
136
136
|
// `requiredWhen`; every consumer calls it, none keeps its own if
|
|
137
137
|
checkEnvRules,
|
|
138
138
|
|
|
139
|
-
// The .env half of the retired-key register (#893): a key that moved into
|
|
140
|
-
// config fails the layer that still declares it, naming the move. Every
|
|
141
|
-
// `.env` read goes through parseEnvFile, so nothing can miss the check
|
|
142
|
-
RETIRED_ENV_KEYS,
|
|
143
|
-
findRetiredEnvKeys,
|
|
144
|
-
assertNoRetiredEnvKeys,
|
|
145
|
-
|
|
146
139
|
// The ONE company resolver (#677): `company: { id }` in, the company's public
|
|
147
140
|
// facts plus its tree on this machine out. The config chain, the .env chain,
|
|
148
141
|
// owner hooks and the desktop signing tree all inherit through its `file()`,
|
|
@@ -162,8 +155,10 @@ module.exports = {
|
|
|
162
155
|
// Writeback (comment-preserving edits + canonical top-level key order)
|
|
163
156
|
applyConfigEdits,
|
|
164
157
|
writeConfigValues,
|
|
158
|
+
writeConfigFileValues,
|
|
165
159
|
applyConfigRemovals,
|
|
166
160
|
removeConfigValues,
|
|
161
|
+
removeConfigFileValues,
|
|
167
162
|
applyCanonicalOrder,
|
|
168
163
|
CANONICAL_TOP_LEVEL_ORDER,
|
|
169
164
|
|
|
@@ -171,6 +166,8 @@ module.exports = {
|
|
|
171
166
|
// heal list the manage walk materializes into a brand's omega.json5
|
|
172
167
|
schemaDefaults,
|
|
173
168
|
missingDefaults,
|
|
169
|
+
planMerge,
|
|
170
|
+
setAtPath,
|
|
174
171
|
defaultComments,
|
|
175
172
|
|
|
176
173
|
// Layer-aware consumer seeding (brand target = no local-layer config at all)
|
|
@@ -244,6 +241,8 @@ module.exports = {
|
|
|
244
241
|
|
|
245
242
|
// Validation
|
|
246
243
|
validateConfig,
|
|
244
|
+
undeclaredPaths,
|
|
245
|
+
undeclaredAuthoredPaths,
|
|
247
246
|
runSchema,
|
|
248
247
|
formatErrors,
|
|
249
248
|
|
|
@@ -254,9 +253,6 @@ module.exports = {
|
|
|
254
253
|
|
|
255
254
|
findSecretKeys,
|
|
256
255
|
SECRET_KEY_PATTERN,
|
|
257
|
-
findRetiredKeys,
|
|
258
|
-
RETIRED_KEYS,
|
|
259
|
-
RETIRED_PATHS,
|
|
260
256
|
|
|
261
257
|
// The one provider shape (#425) — `role.providers.<provider>`, where key
|
|
262
258
|
// presence is the pick and `false` is the deliberate off switch
|
|
@@ -66,7 +66,7 @@ const path = require('node:path');
|
|
|
66
66
|
const JSON5 = require('json5');
|
|
67
67
|
|
|
68
68
|
const { deepMerge, isPlainObject } = require('./merge.js');
|
|
69
|
-
const { resolveCompany, recordBrand } = require('./company.js');
|
|
69
|
+
const { resolveCompany, recordBrand, RESOLVED_COMPANY_KEYS } = require('./company.js');
|
|
70
70
|
const { findSecretKeys } = require('./secrets.js');
|
|
71
71
|
const { validateConfig } = require('./validate.js');
|
|
72
72
|
const { TARGETS } = require('./schema.js');
|
|
@@ -134,10 +134,22 @@ function resolveOverlayEnvironment(environment, fallback) {
|
|
|
134
134
|
function resolveOverlayPath(basePath, environment) {
|
|
135
135
|
if (!basePath || !environment) return null;
|
|
136
136
|
|
|
137
|
-
const overlay =
|
|
137
|
+
const overlay = overlayPath(basePath, environment);
|
|
138
138
|
return fs.existsSync(overlay) ? overlay : null;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* The ONE spelling of a layer's environment overlay: `omega.<environment>.json5`
|
|
143
|
+
* beside its base file, whether or not either exists. Every reader and writer
|
|
144
|
+
* of an overlay names it through here.
|
|
145
|
+
* @param {string} basePath - The layer's omega.json5 path.
|
|
146
|
+
* @param {string} environment - One of ENV_ENVIRONMENTS.
|
|
147
|
+
* @returns {string} Absolute overlay path.
|
|
148
|
+
*/
|
|
149
|
+
function overlayPath(basePath, environment) {
|
|
150
|
+
return path.join(path.dirname(basePath), `${path.basename(FILE_NAME, '.json5')}.${environment}.json5`);
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
/**
|
|
142
154
|
* Cheap probe: does this project have an omega.json5 at all? Frameworks use it
|
|
143
155
|
* to fail soft in non-consumer dirs (seeded-empty config); tooling uses it as
|
|
@@ -301,10 +313,6 @@ function stripTargets(config) {
|
|
|
301
313
|
return shared;
|
|
302
314
|
}
|
|
303
315
|
|
|
304
|
-
// The `company` keys the LOADER fills (#677): authored, they would be
|
|
305
|
-
// overwritten at every load, so an author hears about it at the file.
|
|
306
|
-
const RESOLVED_COMPANY_KEYS = ['name', 'url', 'images'];
|
|
307
|
-
|
|
308
316
|
/**
|
|
309
317
|
* The company keys nobody types, refused where a human writes them: the BRAND
|
|
310
318
|
* file and the COMPANY file (#677). The local layer is deliberately exempt,
|
|
@@ -737,4 +745,4 @@ function composeTargetConfig(projectDir, target, options) {
|
|
|
737
745
|
return { config, files: { local: localPath, brand: brandPath, company: companyPath } };
|
|
738
746
|
}
|
|
739
747
|
|
|
740
|
-
module.exports = { loadConfig, composeTargetConfig, hasOmegaConfig, resolveConfigPath, getEnabledTargets, findBrandRoot, findBrandConfigPath, resolveBrandRoot, FILE_NAME, CONFIG_LOCATIONS, TARGET_SUBDIRS };
|
|
748
|
+
module.exports = { loadConfig, composeTargetConfig, hasOmegaConfig, resolveConfigPath, overlayPath, getEnabledTargets, findBrandRoot, findBrandConfigPath, resolveBrandRoot, FILE_NAME, CONFIG_LOCATIONS, TARGET_SUBDIRS };
|