@aifabrix/builder 2.44.4 → 2.44.6
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/.cursor/rules/cli-layout.mdc +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +68 -17
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/types/wizard.types.js +2 -1
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-app.test-commands.js +9 -5
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +138 -61
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility.js +97 -33
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +225 -19
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -354
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/test-e2e-external.js +4 -3
- package/lib/commands/up-common.js +97 -12
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +58 -15
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +72 -14
- package/lib/commands/wizard-headless.js +7 -3
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +210 -61
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +4 -1
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- package/lib/schema/wizard-config.schema.json +2 -2
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/external-readme.js +117 -4
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +73 -20
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +7 -7
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +89 -102
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -19,6 +19,7 @@ const { updateContainerPortInEnvFile } = require('./env-ports');
|
|
|
19
19
|
const { buildEnvVarMap } = require('./env-map');
|
|
20
20
|
const { getLocalPortFromPath } = require('./port-resolver');
|
|
21
21
|
const { readYamlAtPath, applyCanonicalSecretsOverride } = require('./secrets-canonical');
|
|
22
|
+
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Interpolate ${VAR} occurrences with values from envVars map
|
|
@@ -51,9 +52,13 @@ const { resolveBashKvFromProcessEnv } = require('./secrets-bash-kv');
|
|
|
51
52
|
/**
|
|
52
53
|
* Last-resort when infra.parameter.yaml cannot be read (e.g. Jest suites that mock `fs`;
|
|
53
54
|
* catalog uses `node:fs`, which is often the same mocked instance). Must stay in sync with
|
|
54
|
-
* `generator.type: emptyAllowed` keys in lib/schema/infra.parameter.yaml.
|
|
55
|
+
* `generator.type: emptyAllowed` keys in lib/schema/infra.parameter.yaml; plus optional Azure/OpenAI kv names.
|
|
55
56
|
*/
|
|
56
|
-
const EMPTY_ALLOWED_KV_FALLBACK = new Set([
|
|
57
|
+
const EMPTY_ALLOWED_KV_FALLBACK = new Set([
|
|
58
|
+
'redis-passwordKeyVault',
|
|
59
|
+
'azure-openaiapi-urlKeyVault',
|
|
60
|
+
'secrets-azureOpenaiApiKeyVault'
|
|
61
|
+
]);
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
64
|
* Infra catalog keys with generator `emptyAllowed` may be absent from the secrets file;
|
|
@@ -191,7 +196,12 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
|
|
|
191
196
|
if (secretsFilePaths.buildPath) {
|
|
192
197
|
paths.push(secretsFilePaths.buildPath);
|
|
193
198
|
}
|
|
194
|
-
|
|
199
|
+
let msg = `\n\nSecrets file location: ${paths.join(' and ')}`;
|
|
200
|
+
if (secretsFilePaths.sharedSecretsApiUrl && typeof secretsFilePaths.sharedSecretsApiUrl === 'string') {
|
|
201
|
+
msg +=
|
|
202
|
+
`\n(Shared secrets API: ${secretsFilePaths.sharedSecretsApiUrl.trim()}. Keys from that API are merged when resolving secrets; if a key is still missing, add it via "aifabrix secret set" / shared store or check "aifabrix secret list --shared".)`;
|
|
203
|
+
}
|
|
204
|
+
return msg;
|
|
195
205
|
}
|
|
196
206
|
return '';
|
|
197
207
|
}
|
|
@@ -459,13 +469,7 @@ async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
|
|
|
459
469
|
return updated;
|
|
460
470
|
}
|
|
461
471
|
|
|
462
|
-
/**
|
|
463
|
-
* Validate secrets against the env template (skips commented and empty lines)
|
|
464
|
-
* @function validateSecrets
|
|
465
|
-
* @param {string} envTemplate - Environment template content
|
|
466
|
-
* @param {Object} secrets - Available secrets
|
|
467
|
-
* @returns {Object} Validation result
|
|
468
|
-
*/
|
|
472
|
+
/** Validate secrets vs env template (commented/empty lines skipped). */
|
|
469
473
|
function validateSecrets(envTemplate, secrets) {
|
|
470
474
|
const missing = collectMissingSecrets(envTemplate, secrets);
|
|
471
475
|
return { valid: missing.length === 0, missing };
|
|
@@ -475,6 +479,9 @@ module.exports = {
|
|
|
475
479
|
loadEnvConfig,
|
|
476
480
|
interpolateEnvVars,
|
|
477
481
|
collectMissingSecrets,
|
|
482
|
+
collectUniqueKvPathStrings,
|
|
483
|
+
resolveKvRefValue,
|
|
484
|
+
isKvKeyAllowedEmptyWhenAbsent,
|
|
478
485
|
resolveBashKvFromProcessEnv,
|
|
479
486
|
mergeSecretsWithPrefixedCopies,
|
|
480
487
|
formatMissingSecretsFileInfo,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan env-style content for unique kv:// path segments (comments skipped).
|
|
3
|
+
* @fileoverview Keeps secrets-helpers under max-lines
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const KV_REF_PATTERN = /kv:\/\/([a-zA-Z0-9_\-/]+)/g;
|
|
9
|
+
|
|
10
|
+
function isCommentOrEmptyLine(line) {
|
|
11
|
+
const t = line.trim();
|
|
12
|
+
return t === '' || t.startsWith('#');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} content
|
|
17
|
+
* @returns {string[]}
|
|
18
|
+
*/
|
|
19
|
+
function collectUniqueKvPathStrings(content) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const out = [];
|
|
22
|
+
if (!content || typeof content !== 'string') {
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
for (const line of content.split('\n')) {
|
|
26
|
+
if (isCommentOrEmptyLine(line)) continue;
|
|
27
|
+
let match;
|
|
28
|
+
KV_REF_PATTERN.lastIndex = 0;
|
|
29
|
+
while ((match = KV_REF_PATTERN.exec(line)) !== null) {
|
|
30
|
+
const pathStr = match[1];
|
|
31
|
+
if (!seen.has(pathStr)) {
|
|
32
|
+
seen.add(pathStr);
|
|
33
|
+
out.push(pathStr);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
collectUniqueKvPathStrings
|
|
42
|
+
};
|
|
@@ -8,6 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Treat undefined/null and whitespace-only strings as no value so prefixed slots
|
|
13
|
+
* cannot shadow unprefixed secrets with empty placeholders (plan 117 scoped KV).
|
|
14
|
+
*
|
|
15
|
+
* @param {*} v
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isKvSlotEmpty(v) {
|
|
19
|
+
if (v === undefined || v === null) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (typeof v === 'string' && v.trim() === '') {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
/**
|
|
12
29
|
* @param {Function} getValueByPath - From secrets-helpers
|
|
13
30
|
* @returns {{ getValueByPathWithEnvScope: Function, mergeSecretsWithPrefixedCopies: Function }}
|
|
@@ -27,7 +44,7 @@ function createScopedKvHelpers(getValueByPath) {
|
|
|
27
44
|
const prefix = `${String(envKey).toLowerCase()}-`;
|
|
28
45
|
const prefixedPath = prefix + pathStr;
|
|
29
46
|
const fromPrefixed = getValueByPath(secrets, prefixedPath);
|
|
30
|
-
if (fromPrefixed
|
|
47
|
+
if (!isKvSlotEmpty(fromPrefixed)) {
|
|
31
48
|
return fromPrefixed;
|
|
32
49
|
}
|
|
33
50
|
return getValueByPath(secrets, pathStr);
|
|
@@ -47,7 +64,7 @@ function createScopedKvHelpers(getValueByPath) {
|
|
|
47
64
|
for (const [k, v] of Object.entries(secrets)) {
|
|
48
65
|
if (typeof k !== 'string' || !k || k.startsWith(prefix)) continue;
|
|
49
66
|
const pk = prefix + k;
|
|
50
|
-
if (merged[pk]
|
|
67
|
+
if (isKvSlotEmpty(merged[pk]) && !isKvSlotEmpty(v)) {
|
|
51
68
|
merged[pk] = v;
|
|
52
69
|
}
|
|
53
70
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist resolved kv:// values into the primary user secrets file so regenerate (.env) stays stable.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview After env.template resolves, copy merged secret values into ~/.aifabrix/secrets.local.yaml
|
|
5
|
+
* when keys are missing or empty locally **and** not already provided by the configured shared store
|
|
6
|
+
* (`aifabrix-secrets` remote API or shared YAML), so shared-only keys are not duplicated into user secrets.
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const logger = require('./logger');
|
|
14
|
+
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
15
|
+
const localSecretsModule = require('./local-secrets');
|
|
16
|
+
const secretsUtils = require('./secrets-utils');
|
|
17
|
+
const { loadConfiguredSharedSecretsStore } = require('./remote-secrets-loader');
|
|
18
|
+
const { decryptSecretsObject } = require('../core/secrets-load');
|
|
19
|
+
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
20
|
+
const {
|
|
21
|
+
resolveKvRefValue,
|
|
22
|
+
mergeSecretsWithPrefixedCopies,
|
|
23
|
+
isKvKeyAllowedEmptyWhenAbsent
|
|
24
|
+
} = require('./secrets-helpers');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* After successful kv resolution, write resolved values to the user secrets file when local slot is empty.
|
|
28
|
+
* Does not overwrite non-empty local values (user wins).
|
|
29
|
+
*
|
|
30
|
+
* @async
|
|
31
|
+
* @param {string} templateContent - Original env.template text (still contains kv:// refs)
|
|
32
|
+
* @param {Object} mergedSecrets - Decrypted secrets map passed to resolveKvReferences (same as loadSecrets output)
|
|
33
|
+
* @param {{ effective?: boolean, envKey?: string }|null} scopedKv - Scoped kv context from generateEnvContent
|
|
34
|
+
* @param {Object} [options]
|
|
35
|
+
* @param {boolean} [options.skipMaterializeKvToLocal] - Skip persistence (tests / programmatic)
|
|
36
|
+
* @returns {Promise<string[]>} Keys materialized (written) to local file
|
|
37
|
+
*/
|
|
38
|
+
function buildScopedMaps(mergedSecrets, scopedKv) {
|
|
39
|
+
const effective = Boolean(scopedKv && scopedKv.effective && scopedKv.envKey);
|
|
40
|
+
const secretsMap = effective ? mergeSecretsWithPrefixedCopies(mergedSecrets, scopedKv.envKey) : mergedSecrets;
|
|
41
|
+
const rawLocal = secretsUtils.loadPrimaryUserSecrets();
|
|
42
|
+
const localMap = effective ? mergeSecretsWithPrefixedCopies(rawLocal, scopedKv.envKey) : rawLocal;
|
|
43
|
+
return { effective, secretsMap, localMap };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shouldSkipPersist(pathStr, resolvedVal, localVal) {
|
|
47
|
+
if (resolvedVal === undefined || resolvedVal === null) return true;
|
|
48
|
+
if (
|
|
49
|
+
typeof resolvedVal === 'string' &&
|
|
50
|
+
resolvedVal.trim() === '' &&
|
|
51
|
+
isKvKeyAllowedEmptyWhenAbsent(pathStr)
|
|
52
|
+
) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (localVal !== undefined && localVal !== null && String(localVal).trim() !== '') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isNonEmptyResolvedKv(val) {
|
|
62
|
+
if (val === undefined || val === null) return false;
|
|
63
|
+
if (typeof val === 'string' && val.trim() === '') return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveSharedScopedMapForMaterialize(scopedKv, effective) {
|
|
68
|
+
try {
|
|
69
|
+
const rawShared = await loadConfiguredSharedSecretsStore();
|
|
70
|
+
const decryptedShared = rawShared ? await decryptSecretsObject(rawShared) : null;
|
|
71
|
+
if (!decryptedShared) return null;
|
|
72
|
+
return effective && scopedKv && scopedKv.envKey
|
|
73
|
+
? mergeSecretsWithPrefixedCopies(decryptedShared, scopedKv.envKey)
|
|
74
|
+
: decryptedShared;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function plaintextKvForLocalPersist(pathStr, secretsMap, localMap, sharedScoped, scopedKv, effective) {
|
|
81
|
+
const resolvedVal = resolveKvRefValue(secretsMap, pathStr, scopedKv?.envKey, effective);
|
|
82
|
+
const localVal = resolveKvRefValue(localMap, pathStr, scopedKv?.envKey, effective);
|
|
83
|
+
if (shouldSkipPersist(pathStr, resolvedVal, localVal)) return null;
|
|
84
|
+
const sharedVal =
|
|
85
|
+
sharedScoped && typeof sharedScoped === 'object'
|
|
86
|
+
? resolveKvRefValue(sharedScoped, pathStr, scopedKv?.envKey, effective)
|
|
87
|
+
: undefined;
|
|
88
|
+
if (isNonEmptyResolvedKv(sharedVal)) return null;
|
|
89
|
+
return typeof resolvedVal === 'string' ? resolvedVal : String(resolvedVal);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function materializeResolvedKvSecretsToUserLocal(templateContent, mergedSecrets, scopedKv, options = {}) {
|
|
93
|
+
if (
|
|
94
|
+
!templateContent ||
|
|
95
|
+
typeof templateContent !== 'string' ||
|
|
96
|
+
options.skipMaterializeKvToLocal === true
|
|
97
|
+
) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const { effective, secretsMap, localMap } = buildScopedMaps(mergedSecrets, scopedKv);
|
|
103
|
+
const sharedScoped = await resolveSharedScopedMapForMaterialize(scopedKv, effective);
|
|
104
|
+
|
|
105
|
+
const paths = collectUniqueKvPathStrings(templateContent);
|
|
106
|
+
const materialized = [];
|
|
107
|
+
|
|
108
|
+
for (const pathStr of paths) {
|
|
109
|
+
const plain = plaintextKvForLocalPersist(pathStr, secretsMap, localMap, sharedScoped, scopedKv, effective);
|
|
110
|
+
if (plain === null) continue;
|
|
111
|
+
await localSecretsModule.saveLocalSecret(pathStr, plain);
|
|
112
|
+
materialized.push(pathStr);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (materialized.length > 0) {
|
|
116
|
+
logger.log(
|
|
117
|
+
formatSuccessLine(
|
|
118
|
+
`Saved ${materialized.length} resolved kv key(s) to local secrets for stable reinstalls: ${materialized.join(', ')}`
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return materialized;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (typeof logger.warn === 'function') {
|
|
126
|
+
logger.warn(`Could not materialize resolved kv secrets to local file: ${err.message}`);
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
materializeResolvedKvSecretsToUserLocal
|
|
134
|
+
};
|
|
@@ -13,6 +13,15 @@ const path = require('path');
|
|
|
13
13
|
const config = require('../core/config');
|
|
14
14
|
const paths = require('./paths');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} s
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isHttpOrHttpsUrl(s) {
|
|
21
|
+
const t = String(s || '').trim();
|
|
22
|
+
return t.startsWith('http://') || t.startsWith('https://');
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* Resolves secrets file path when an explicit path is provided.
|
|
18
27
|
* If not provided, returns default fallback under <home>/secrets.yaml.
|
|
@@ -42,7 +51,8 @@ function resolveSecretsPath(secretsPath) {
|
|
|
42
51
|
* @param {string} [_appName] - Application name (optional, for backward compatibility, unused)
|
|
43
52
|
* @returns {Promise<Object>} Object with userPath and buildPath (if configured)
|
|
44
53
|
* @returns {string} returns.userPath - User's secrets file path (~/.aifabrix/secrets.local.yaml)
|
|
45
|
-
* @returns {string|null} returns.buildPath -
|
|
54
|
+
* @returns {string|null} returns.buildPath - On-disk shared secrets file (if configured; never an http(s) URL)
|
|
55
|
+
* @returns {string|null} [returns.sharedSecretsApiUrl] - When aifabrix-secrets is an API URL (for error messages; loadSecrets merges this API into the kv:// resolution map)
|
|
46
56
|
*/
|
|
47
57
|
async function getActualSecretsPath(secretsPath, _appName) {
|
|
48
58
|
// If explicit path provided, use it (backward compatibility)
|
|
@@ -50,30 +60,34 @@ async function getActualSecretsPath(secretsPath, _appName) {
|
|
|
50
60
|
const resolvedPath = resolveSecretsPath(secretsPath);
|
|
51
61
|
return {
|
|
52
62
|
userPath: resolvedPath,
|
|
53
|
-
buildPath: null
|
|
63
|
+
buildPath: null,
|
|
64
|
+
sharedSecretsApiUrl: null
|
|
54
65
|
};
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
// Cascading lookup: user's file first (
|
|
58
|
-
const userSecretsPath =
|
|
68
|
+
// Cascading lookup: user's file first (same path as `secret set`: getPrimaryUserSecretsLocalPath)
|
|
69
|
+
const userSecretsPath = paths.getPrimaryUserSecretsLocalPath();
|
|
59
70
|
|
|
60
|
-
// Check config.yaml for canonical secrets path
|
|
61
71
|
let buildSecretsPath = null;
|
|
72
|
+
let sharedSecretsApiUrl = null;
|
|
62
73
|
try {
|
|
63
74
|
const canonicalSecretsPath = await config.getAifabrixSecretsPath();
|
|
64
75
|
if (canonicalSecretsPath) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
const raw = String(canonicalSecretsPath).trim();
|
|
77
|
+
if (isHttpOrHttpsUrl(raw)) {
|
|
78
|
+
sharedSecretsApiUrl = raw.replace(/\/+$/, '');
|
|
79
|
+
} else {
|
|
80
|
+
buildSecretsPath = path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
|
81
|
+
}
|
|
68
82
|
}
|
|
69
83
|
} catch (error) {
|
|
70
84
|
// Ignore errors, continue
|
|
71
85
|
}
|
|
72
86
|
|
|
73
|
-
// Return both paths (even if files don't exist) for error messages
|
|
74
87
|
return {
|
|
75
88
|
userPath: userSecretsPath,
|
|
76
|
-
buildPath: buildSecretsPath
|
|
89
|
+
buildPath: buildSecretsPath,
|
|
90
|
+
sharedSecretsApiUrl
|
|
77
91
|
};
|
|
78
92
|
}
|
|
79
93
|
|
|
@@ -58,7 +58,7 @@ async function loadSecretsFromFile(filePath) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Loads user secrets from getPrimaryUserSecretsLocalPath() (
|
|
61
|
+
* Loads user secrets from getPrimaryUserSecretsLocalPath() (under {@link module:lib/utils/paths.getAifabrixHome}).
|
|
62
62
|
* Used as the master source when merging with project/public secrets: user values win,
|
|
63
63
|
* missing keys are filled from the public (aifabrix-secrets) file.
|
|
64
64
|
*
|
|
@@ -128,7 +128,7 @@ function loadDefaultSecrets() {
|
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* Creates the primary user secrets file if missing (empty map) for first-run installs.
|
|
131
|
-
* Uses the same directory as {@link loadPrimaryUserSecrets} (
|
|
131
|
+
* Uses the same directory as {@link loadPrimaryUserSecrets} (resolved AI Fabrix home).
|
|
132
132
|
*
|
|
133
133
|
* @function ensurePrimaryUserSecretsFileExists
|
|
134
134
|
*/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picks the parent directory for platform `builder/<app>` materialization when the project has no
|
|
3
|
+
* `builder/<app>`. Compares the effective config directory (`getAifabrixSystemDir`) to the resolved
|
|
4
|
+
* AI Fabrix home (`getAifabrixHome`, from `AIFABRIX_HOME` or `aifabrix-home` in config.yaml).
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview System builder root parent (config dir vs home override)
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* When the effective config directory lies under the resolved AI Fabrix home, keep state (including
|
|
17
|
+
* `builder/`) beside `config.yaml` (e.g. `$HOME/.aifabrix/builder` when `AIFABRIX_HOME=$HOME`).
|
|
18
|
+
* When `aifabrix-home` relocates home outside the config tree, materialize under that home instead.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} systemDir - Absolute config/state directory (same as `getAifabrixSystemDir()`).
|
|
21
|
+
* @param {string} homeDir - Absolute AI Fabrix home (`getAifabrixHome()`).
|
|
22
|
+
* @returns {string} Absolute directory whose `builder/` subdir is used
|
|
23
|
+
*/
|
|
24
|
+
function resolveSystemBuilderParentDir(systemDir, homeDir) {
|
|
25
|
+
const s = path.resolve(systemDir);
|
|
26
|
+
const h = path.resolve(homeDir);
|
|
27
|
+
const rel = path.relative(h, s);
|
|
28
|
+
const configUnderHome = rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
29
|
+
return configUnderHome ? s : h;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
resolveSystemBuilderParentDir
|
|
34
|
+
};
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
const { computePathActive } = require('./url-declarative-url-flags');
|
|
20
20
|
const { loadApplicationYamlDocForUrlResolve } = require('./url-declarative-resolve-load-doc');
|
|
21
21
|
const { parseUrlToken } = require('./url-declarative-token-parse');
|
|
22
|
+
const runtimeBasePath = require('./url-declarative-runtime-base-path');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Plan 122: **Developer subdomain `devNN` + remote hostname is not derived from envKey `tst`.** It comes only from
|
|
@@ -209,6 +210,7 @@ function buildInternalUrlString(opts) {
|
|
|
209
210
|
profile,
|
|
210
211
|
listenPort,
|
|
211
212
|
targetAppKey,
|
|
213
|
+
runtimeBasePath,
|
|
212
214
|
remoteServer,
|
|
213
215
|
pathPrefix,
|
|
214
216
|
patternPath,
|
|
@@ -223,7 +225,8 @@ function buildInternalUrlString(opts) {
|
|
|
223
225
|
declarativeCurrentAppKey
|
|
224
226
|
} = opts;
|
|
225
227
|
if (profile === 'docker') {
|
|
226
|
-
|
|
228
|
+
const origin = `http://${targetAppKey}:${listenPort}`;
|
|
229
|
+
return runtimeBasePath ? joinUrlPath(origin, runtimeBasePath) : origin;
|
|
227
230
|
}
|
|
228
231
|
if (remoteServer && String(remoteServer).trim()) {
|
|
229
232
|
return buildPublicUrlString({
|
|
@@ -432,10 +435,12 @@ function expandFullSurfaceInternal(r) {
|
|
|
432
435
|
if (isLocalProfileWithoutRemoteServer(r)) {
|
|
433
436
|
return expandFullSurfacePublic(r);
|
|
434
437
|
}
|
|
438
|
+
const runtimePath = runtimeBasePath.normalizeRuntimeBasePath(r.patternPath, r);
|
|
435
439
|
return buildInternalUrlString({
|
|
436
440
|
profile: r.profile,
|
|
437
441
|
listenPort: r.listenPort,
|
|
438
442
|
targetAppKey: r.appKey,
|
|
443
|
+
runtimeBasePath: runtimePath,
|
|
439
444
|
remoteServer: r.remoteServer,
|
|
440
445
|
pathPrefix: r.pathPrefix,
|
|
441
446
|
patternPath: r.patternPath,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime base path helpers for declarative url:// resolution.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Keeps path-aware internal URLs out of the main resolver file.
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Runtime base path is derived from the existing frontDoorRouting path only
|
|
13
|
+
* when that path is active for the target app.
|
|
14
|
+
* @param {string|null|undefined} patternPath
|
|
15
|
+
* @param {Object} r
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function normalizeRuntimeBasePath(patternPath, r) {
|
|
19
|
+
if (!r.frontDoorIngressActive) {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
const value = String(patternPath || '').trim();
|
|
23
|
+
if (!value || value === '/') {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const pathValue = value.startsWith('/') ? value : `/${value}`;
|
|
27
|
+
return pathValue.replace(/\/{2,}/g, '/').replace(/\/+$/, '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
normalizeRuntimeBasePath
|
|
32
|
+
};
|
|
@@ -95,5 +95,6 @@ module.exports = {
|
|
|
95
95
|
URL_DECLARATIVE_VDIR_PUBLIC_TOKEN,
|
|
96
96
|
INACTIVE_VDIR_PUBLIC_ENV_FALLBACK,
|
|
97
97
|
applyInactiveVdirPublicTokenRewrite,
|
|
98
|
-
rewriteInactiveDeclarativeVdirPublicContent
|
|
98
|
+
rewriteInactiveDeclarativeVdirPublicContent,
|
|
99
|
+
isFrontDoorRoutingEnabledInDoc
|
|
99
100
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* urls.local.yaml
|
|
3
|
-
* When
|
|
4
|
-
* is $HOME/.aifabrix/urls.local.yaml (not $HOME/urls.local.yaml).
|
|
2
|
+
* Primary `urls.local.yaml` under {@link module:lib/utils/paths.getAifabrixHome} (same as
|
|
3
|
+
* `secrets.local.yaml`). When that file is missing, falls back to the config directory for migration.
|
|
5
4
|
*
|
|
6
5
|
* @fileoverview Read/write registry; scan each builder app folder for application.yaml
|
|
7
6
|
* @author AI Fabrix Team
|
|
@@ -17,15 +16,15 @@ const { DECLARATIVE_URL_INFRA_DEFAULTS } = require('./infra-env-defaults');
|
|
|
17
16
|
const pathsUtil = require('./paths');
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
|
-
* @returns {string} Absolute path to urls.local.yaml (primary;
|
|
19
|
+
* @returns {string} Absolute path to urls.local.yaml (primary; under resolved AI Fabrix home)
|
|
21
20
|
*/
|
|
22
21
|
function getUrlsLocalYamlPath() {
|
|
23
|
-
return path.join(pathsUtil.
|
|
22
|
+
return path.join(pathsUtil.getAifabrixHome(), 'urls.local.yaml');
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
/** @returns {string}
|
|
27
|
-
function
|
|
28
|
-
return path.join(pathsUtil.
|
|
25
|
+
/** @returns {string} Path beside config.yaml (migration / older layouts) */
|
|
26
|
+
function getConfigDirUrlsLocalYamlPath() {
|
|
27
|
+
return path.join(pathsUtil.getConfigDirForPaths(), 'urls.local.yaml');
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
function loadRegistryYamlFile(filePath) {
|
|
@@ -45,9 +44,9 @@ function readUrlsLocalRegistrySync() {
|
|
|
45
44
|
if (fsRealSync.existsSync(primary)) {
|
|
46
45
|
return loadRegistryYamlFile(primary);
|
|
47
46
|
}
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
return loadRegistryYamlFile(
|
|
47
|
+
const atConfig = getConfigDirUrlsLocalYamlPath();
|
|
48
|
+
if (path.resolve(atConfig) !== path.resolve(primary) && fsRealSync.existsSync(atConfig)) {
|
|
49
|
+
return loadRegistryYamlFile(atConfig);
|
|
51
50
|
}
|
|
52
51
|
return {};
|
|
53
52
|
}
|
|
@@ -175,6 +174,54 @@ function mergeBuilderDirIntoRegistry(merged, builderDir) {
|
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
176
|
|
|
177
|
+
/**
|
|
178
|
+
* True when getBuilderRoot() resolves to the same path as AIFABRIX_BUILDER_DIR (authoritative override).
|
|
179
|
+
* @param {string|null} resolvedEffective
|
|
180
|
+
* @param {string|null} envResolved
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
function effectiveBuilderMatchesEnvVar(resolvedEffective, envResolved) {
|
|
184
|
+
return Boolean(envResolved && resolvedEffective && resolvedEffective === envResolved);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Builder dirs to scan in order; later merges overwrite registry keys from earlier dirs.
|
|
189
|
+
* When `AIFABRIX_BUILDER_DIR` is set and differs from projectRoot/builder, env builder root is merged last.
|
|
190
|
+
* Otherwise projectRoot/builder is merged last so explicit refresh roots override getBuilderRoot().
|
|
191
|
+
*
|
|
192
|
+
* @param {string} root - Resolved project root passed to refresh
|
|
193
|
+
* @param {string|null} effectiveBuilderDir - pathsUtil.getBuilderRoot()
|
|
194
|
+
* @returns {string[]}
|
|
195
|
+
*/
|
|
196
|
+
function getOrderedBuilderDirsForRegistryScan(root, effectiveBuilderDir) {
|
|
197
|
+
const legacyBuilderDir = path.join(root, 'builder');
|
|
198
|
+
let resolvedLegacy;
|
|
199
|
+
let resolvedEffective;
|
|
200
|
+
try {
|
|
201
|
+
resolvedLegacy = path.resolve(legacyBuilderDir);
|
|
202
|
+
resolvedEffective = effectiveBuilderDir ? path.resolve(effectiveBuilderDir) : null;
|
|
203
|
+
} catch {
|
|
204
|
+
return [legacyBuilderDir];
|
|
205
|
+
}
|
|
206
|
+
const envRaw = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
|
|
207
|
+
const envResolved = envRaw ? path.resolve(envRaw) : null;
|
|
208
|
+
// Only treat env as authoritative when getBuilderRoot() is actually that path. Otherwise a stray
|
|
209
|
+
// AIFABRIX_BUILDER_DIR on CI (or Jest mocking getBuilderRoot to a temp dir) must not force
|
|
210
|
+
// [legacy, effective] — that order lets the real checkout builder overwrite the mocked root last.
|
|
211
|
+
const effectiveMatchesEnvVar = effectiveBuilderMatchesEnvVar(resolvedEffective, envResolved);
|
|
212
|
+
|
|
213
|
+
if (effectiveBuilderDir && resolvedEffective && resolvedEffective === resolvedLegacy) {
|
|
214
|
+
return [legacyBuilderDir];
|
|
215
|
+
}
|
|
216
|
+
if (effectiveMatchesEnvVar && effectiveBuilderDir && resolvedEffective && resolvedEffective !== resolvedLegacy) {
|
|
217
|
+
return [legacyBuilderDir, effectiveBuilderDir];
|
|
218
|
+
}
|
|
219
|
+
if (effectiveBuilderDir && resolvedEffective && resolvedEffective !== resolvedLegacy) {
|
|
220
|
+
return [effectiveBuilderDir, legacyBuilderDir];
|
|
221
|
+
}
|
|
222
|
+
return [legacyBuilderDir];
|
|
223
|
+
}
|
|
224
|
+
|
|
178
225
|
/**
|
|
179
226
|
* Merge scan results into registry (does not remove stale keys).
|
|
180
227
|
* @param {string|null} projectRoot - getProjectRoot() or null (same semantics as projectRoot || getProjectRoot())
|
|
@@ -188,18 +235,24 @@ function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
|
|
|
188
235
|
}
|
|
189
236
|
// Published npm tarball omits builder/ under the package root (.npmignore). Global installs must
|
|
190
237
|
// still refresh from the real builder tree (AIFABRIX_BUILDER_DIR or integration base + builder).
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
238
|
+
let effectiveBuilderDir = null;
|
|
239
|
+
try {
|
|
240
|
+
effectiveBuilderDir = pathsUtil.getBuilderRoot();
|
|
241
|
+
} catch {
|
|
242
|
+
effectiveBuilderDir = null;
|
|
243
|
+
}
|
|
244
|
+
let builderDirs = getOrderedBuilderDirsForRegistryScan(root, effectiveBuilderDir);
|
|
194
245
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
246
|
+
const sysRoot = pathsUtil.getSystemBuilderRoot();
|
|
247
|
+
const resolvedSys = path.resolve(sysRoot);
|
|
248
|
+
if (fsRealSync.existsSync(sysRoot)) {
|
|
249
|
+
const resolvedList = builderDirs.map((d) => path.resolve(d));
|
|
250
|
+
if (!resolvedList.includes(resolvedSys)) {
|
|
251
|
+
builderDirs = [sysRoot, ...builderDirs];
|
|
252
|
+
}
|
|
200
253
|
}
|
|
201
254
|
} catch {
|
|
202
|
-
|
|
255
|
+
// ignore
|
|
203
256
|
}
|
|
204
257
|
for (const builderDir of builderDirs) {
|
|
205
258
|
mergeBuilderDirIntoRegistry(merged, builderDir);
|