@aifabrix/builder 2.44.5 → 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 +48 -2
- 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/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.js +20 -2
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +134 -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 +78 -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 +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- 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/up-common.js +79 -19
- 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 +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- 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/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 +1 -1
- 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 +71 -2
- 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 +23 -12
- 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 +1 -1
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- 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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map secrets keys `BASH_<NAME>` to process-style env `{ [NAME]: value }` for child_process env.
|
|
3
|
+
* Same naming rule as kv://BASH_* resolution (see secrets-bash-kv.js).
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview BASH-prefixed secret → exported-style env for Docker/subprocess
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const secretsLoad = require('../core/secrets-load');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} suffix - Part after BASH_
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isValidExportedName(suffix) {
|
|
19
|
+
return Boolean(suffix && /^[A-Za-z_][A-Za-z0-9_]*$/.test(suffix));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Collect `BASH_*` entries from a flat or shallow secrets object.
|
|
24
|
+
*
|
|
25
|
+
* @param {Record<string, unknown>|null|undefined} secrets - Merged secrets map (decrypted)
|
|
26
|
+
* @returns {Record<string, string>} e.g. { NPM_TOKEN: '...' } from BASH_NPM_TOKEN
|
|
27
|
+
*/
|
|
28
|
+
function collectBashPrefixedEnv(secrets) {
|
|
29
|
+
const out = {};
|
|
30
|
+
if (!secrets || typeof secrets !== 'object') return out;
|
|
31
|
+
for (const [k, v] of Object.entries(secrets)) {
|
|
32
|
+
if (typeof k !== 'string' || !k.startsWith('BASH_')) continue;
|
|
33
|
+
if (v === undefined || v === null) continue;
|
|
34
|
+
const str = typeof v === 'string' ? v.trim() : String(v).trim();
|
|
35
|
+
if (!str) continue;
|
|
36
|
+
const suffix = k.slice(5);
|
|
37
|
+
if (!isValidExportedName(suffix)) continue;
|
|
38
|
+
out[suffix] = str;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Overlay for `child_process` `env`: values from user + shared + ancestor `secrets.local.yaml`
|
|
45
|
+
* for every `BASH_*` key (merged via {@link secretsLoad.loadSecrets}).
|
|
46
|
+
*
|
|
47
|
+
* @param {string|null} [secretsPath] - Optional explicit secrets file (same as resolve)
|
|
48
|
+
* @param {string|null} [appName] - Optional app name for loadSecrets second arg
|
|
49
|
+
* @returns {Promise<Record<string, string>>}
|
|
50
|
+
*/
|
|
51
|
+
async function getBashPrefixedProcessEnvOverlay(secretsPath = null, appName = null) {
|
|
52
|
+
const secrets = await secretsLoad.loadSecrets(secretsPath, appName);
|
|
53
|
+
return collectBashPrefixedEnv(secrets);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
collectBashPrefixedEnv,
|
|
58
|
+
getBashPrefixedProcessEnvOverlay
|
|
59
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Missing-secrets CLI error lines (layout colors via cli-layout-chalk).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { metadata, sectionTitle } = require('./cli-layout-chalk');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string[]} messages - Output lines
|
|
14
|
+
* @param {RegExpMatchArray|null} missingSecretsMatch - Missing secrets capture
|
|
15
|
+
*/
|
|
16
|
+
function pushMissingSecretsBodyLines(messages, missingSecretsMatch) {
|
|
17
|
+
if (missingSecretsMatch) {
|
|
18
|
+
const parts = missingSecretsMatch[1]
|
|
19
|
+
.trim()
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((s) => s.trim())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
if (parts.length > 1) {
|
|
24
|
+
messages.push(` ${sectionTitle('Missing secrets:')}`);
|
|
25
|
+
for (const ref of parts) {
|
|
26
|
+
messages.push(` ${chalk.cyan('-')} ${chalk.white(ref)}`);
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (parts.length === 1) {
|
|
31
|
+
messages.push(` ${sectionTitle('Missing secrets:')} ${chalk.white(parts[0])}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
messages.push(` ${sectionTitle('Missing secrets:')}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
messages.push(` ${chalk.white('Missing secrets in secrets file.')}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string[]} messages - Output lines
|
|
42
|
+
* @param {RegExpMatchArray|null} fileInfoMatch - File location capture
|
|
43
|
+
* @param {RegExpMatchArray|null} resolveMatch - Resolve command capture
|
|
44
|
+
*/
|
|
45
|
+
function pushSecretsResolutionHintLines(messages, fileInfoMatch, resolveMatch) {
|
|
46
|
+
if (fileInfoMatch) {
|
|
47
|
+
messages.push(` ${metadata('Secrets file location:')} ${chalk.white(fileInfoMatch[1])}`);
|
|
48
|
+
}
|
|
49
|
+
if (resolveMatch) {
|
|
50
|
+
messages.push(
|
|
51
|
+
` ${metadata('Run:')} ${chalk.yellow(`aifabrix resolve ${resolveMatch[1]} to generate missing secrets.`)}`
|
|
52
|
+
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
messages.push(` ${metadata('Run:')} ${chalk.yellow('aifabrix resolve <app-name> to generate missing secrets.')}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Format secrets-related errors
|
|
60
|
+
* @param {string} errorMsg - Error message
|
|
61
|
+
* @returns {string[]|null} Array of error message lines or null if not a secrets error
|
|
62
|
+
*/
|
|
63
|
+
function formatSecretsError(errorMsg) {
|
|
64
|
+
if (!errorMsg.includes('Missing secrets')) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const messages = [];
|
|
69
|
+
pushMissingSecretsBodyLines(messages, errorMsg.match(/Missing secrets: ([^\n]+)/));
|
|
70
|
+
pushSecretsResolutionHintLines(
|
|
71
|
+
messages,
|
|
72
|
+
errorMsg.match(/Secrets file location: ([^\n]+)/),
|
|
73
|
+
errorMsg.match(/Run "aifabrix resolve ([^"]+)"/)
|
|
74
|
+
);
|
|
75
|
+
return messages;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { formatSecretsError };
|
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Invoke chalk[method](text) when the mock/real chalk exposes that method; otherwise return text.
|
|
12
|
+
* Keeps layout helpers working in Jest suites that only stub a subset of chalk.
|
|
13
|
+
* @param {string} method - chalk method name (e.g. 'white', 'bold', 'gray')
|
|
14
|
+
* @param {string} text
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function chalkStyle(method, text) {
|
|
18
|
+
const fn = chalk[method];
|
|
19
|
+
return typeof fn === 'function' ? fn(text) : text;
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
/** Canonical success glyph (green), layout §CORE / §3. */
|
|
11
23
|
function successGlyph() {
|
|
12
24
|
return chalk.green('✔');
|
|
@@ -35,6 +47,15 @@ function formatSuccessParagraph(message) {
|
|
|
35
47
|
return chalk.green(`\n✔ ${message}`);
|
|
36
48
|
}
|
|
37
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Non-blocking warning (layout §CORE): yellow ⚠ + white detail.
|
|
52
|
+
* @param {string} message - text after the glyph (do not prefix with ⚠)
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function formatWarningLine(message) {
|
|
56
|
+
return `${chalkStyle('yellow', '⚠')} ${chalkStyle('white', message)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
/**
|
|
39
60
|
* Blocking error line: red ✖ + red message (layout §18).
|
|
40
61
|
* @param {string} message
|
|
@@ -65,7 +86,7 @@ function formatIssue(title, hint) {
|
|
|
65
86
|
*/
|
|
66
87
|
function formatNextActions(lines) {
|
|
67
88
|
const body = (lines || [])
|
|
68
|
-
.map(line => `${
|
|
89
|
+
.map(line => `${chalkStyle('cyan', '-')} ${chalkStyle('white', line)}`)
|
|
69
90
|
.join('\n');
|
|
70
91
|
return `${sectionTitle('Next actions:')}\n${body}`;
|
|
71
92
|
}
|
|
@@ -87,7 +108,7 @@ function formatDocsLine(label, url) {
|
|
|
87
108
|
* @returns {string}
|
|
88
109
|
*/
|
|
89
110
|
function formatProgress(message) {
|
|
90
|
-
return `${
|
|
111
|
+
return `${chalkStyle('yellow', '⏳')} ${chalkStyle('white', message)}`;
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
/**
|
|
@@ -99,7 +120,7 @@ function formatProgress(message) {
|
|
|
99
120
|
*/
|
|
100
121
|
function formatBulletSection(title, items, opts) {
|
|
101
122
|
const bulletColor = opts && opts.bullet === 'red' ? chalk.red : chalk.cyan;
|
|
102
|
-
const body = (items || []).map(line => `${bulletColor('-')} ${
|
|
123
|
+
const body = (items || []).map(line => `${bulletColor('-')} ${chalkStyle('white', line)}`).join('\n');
|
|
103
124
|
return `${sectionTitle(title)}\n${body}`;
|
|
104
125
|
}
|
|
105
126
|
|
|
@@ -109,7 +130,7 @@ function formatBulletSection(title, items, opts) {
|
|
|
109
130
|
* @returns {string}
|
|
110
131
|
*/
|
|
111
132
|
function sectionTitle(text) {
|
|
112
|
-
return
|
|
133
|
+
return chalkStyle('bold', chalkStyle('white', text));
|
|
113
134
|
}
|
|
114
135
|
|
|
115
136
|
/**
|
|
@@ -119,7 +140,7 @@ function sectionTitle(text) {
|
|
|
119
140
|
* @returns {string}
|
|
120
141
|
*/
|
|
121
142
|
function headerKeyValue(label, value) {
|
|
122
|
-
return `${
|
|
143
|
+
return `${chalkStyle('gray', label)} ${chalkStyle('bold', chalkStyle('white', value))}`;
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
/**
|
|
@@ -201,7 +222,7 @@ function formatDatasourceListRow(rowStatus, name, statusHint) {
|
|
|
201
222
|
? chalk.red('✖')
|
|
202
223
|
: chalk.gray('⏭');
|
|
203
224
|
const hint = statusHint ? ` ${chalk.gray(`(${statusHint})`)}` : '';
|
|
204
|
-
return ` ${sym} ${
|
|
225
|
+
return ` ${sym} ${chalkStyle('white', name)}${hint}`;
|
|
205
226
|
}
|
|
206
227
|
|
|
207
228
|
/**
|
|
@@ -232,13 +253,13 @@ function colorRollupPrefixedLine(line) {
|
|
|
232
253
|
const trimmed = line.trimStart();
|
|
233
254
|
const first = trimmed[0];
|
|
234
255
|
if (first === '✔') {
|
|
235
|
-
return chalk.green('✔') +
|
|
256
|
+
return chalk.green('✔') + chalkStyle('white', trimmed.slice(1));
|
|
236
257
|
}
|
|
237
258
|
if (first === '⚠') {
|
|
238
|
-
return chalk.yellow('⚠') +
|
|
259
|
+
return chalk.yellow('⚠') + chalkStyle('white', trimmed.slice(1));
|
|
239
260
|
}
|
|
240
261
|
if (first === '✖') {
|
|
241
|
-
return chalk.red('✖') +
|
|
262
|
+
return chalk.red('✖') + chalkStyle('white', trimmed.slice(1));
|
|
242
263
|
}
|
|
243
264
|
return line;
|
|
244
265
|
}
|
|
@@ -258,6 +279,7 @@ module.exports = {
|
|
|
258
279
|
failureGlyph,
|
|
259
280
|
formatSuccessLine,
|
|
260
281
|
formatSuccessParagraph,
|
|
282
|
+
formatWarningLine,
|
|
261
283
|
formatBlockingError,
|
|
262
284
|
formatIssue,
|
|
263
285
|
formatNextActions,
|
package/lib/utils/cli-utils.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const logger = require('./logger');
|
|
14
|
+
const { formatBlockingError, infoLine } = require('./cli-layout-chalk');
|
|
15
|
+
const { formatSecretsError } = require('./cli-secrets-error-format');
|
|
14
16
|
const { getDockerDaemonStartHintSentence, getDockerApiOverTcpHintLines } = require('./docker-not-running-hint');
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -231,40 +233,6 @@ function formatAzureError(errorMsg) {
|
|
|
231
233
|
return null;
|
|
232
234
|
}
|
|
233
235
|
|
|
234
|
-
/**
|
|
235
|
-
* Format secrets-related errors
|
|
236
|
-
* @param {string} errorMsg - Error message
|
|
237
|
-
* @returns {string[]|null} Array of error message lines or null if not a secrets error
|
|
238
|
-
*/
|
|
239
|
-
function formatSecretsError(errorMsg) {
|
|
240
|
-
if (!errorMsg.includes('Missing secrets')) {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const messages = [];
|
|
245
|
-
const missingSecretsMatch = errorMsg.match(/Missing secrets: ([^\n]+)/);
|
|
246
|
-
const fileInfoMatch = errorMsg.match(/Secrets file location: ([^\n]+)/);
|
|
247
|
-
const resolveMatch = errorMsg.match(/Run "aifabrix resolve ([^"]+)"/);
|
|
248
|
-
|
|
249
|
-
if (missingSecretsMatch) {
|
|
250
|
-
messages.push(` Missing secrets: ${missingSecretsMatch[1]}`);
|
|
251
|
-
} else {
|
|
252
|
-
messages.push(' Missing secrets in secrets file.');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (fileInfoMatch) {
|
|
256
|
-
messages.push(` Secrets file location: ${fileInfoMatch[1]}`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (resolveMatch) {
|
|
260
|
-
messages.push(` Run: aifabrix resolve ${resolveMatch[1]} to generate missing secrets.`);
|
|
261
|
-
} else {
|
|
262
|
-
messages.push(' Run: aifabrix resolve <app-name> to generate missing secrets.');
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return messages;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
236
|
/**
|
|
269
237
|
* Format deployment-related errors
|
|
270
238
|
* @param {string} errorMsg - Error message
|
|
@@ -388,9 +356,9 @@ function formatError(error) {
|
|
|
388
356
|
* @param {string[]} errorMessages - Error message lines
|
|
389
357
|
*/
|
|
390
358
|
function logError(command, errorMessages) {
|
|
391
|
-
logger.error(`\n
|
|
359
|
+
logger.error(`\n${formatBlockingError(`Error in ${command} command:`)}`);
|
|
392
360
|
errorMessages.forEach(msg => logger.error(msg));
|
|
393
|
-
logger.
|
|
361
|
+
logger.log(`\n${infoLine('ℹ Run "aifabrix doctor" for environment diagnostics.')}\n`);
|
|
394
362
|
}
|
|
395
363
|
|
|
396
364
|
/**
|
|
@@ -300,12 +300,20 @@ function appendValidationIssueLines(lines, envelope, maxIssues = 5) {
|
|
|
300
300
|
const code = iss && iss.code ? chalk.red(`[${iss.code}] `) : '';
|
|
301
301
|
const msg = iss && iss.message ? String(iss.message) : JSON.stringify(iss);
|
|
302
302
|
lines.push(` ${code}${chalk.yellow(msg)}`);
|
|
303
|
+
appendDpSec013Details(lines, iss);
|
|
303
304
|
}
|
|
304
305
|
if (issues.length > cap) {
|
|
305
306
|
lines.push(chalk.gray(` … and ${issues.length - cap} more (see --json or debug full/raw)`));
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
function appendDpSec013Details(lines, iss) {
|
|
311
|
+
if (!iss || iss.code !== 'DP-SEC-013') return;
|
|
312
|
+
const perm = iss.details && iss.details.resolvedPermission ? String(iss.details.resolvedPermission) : '';
|
|
313
|
+
if (!perm) return;
|
|
314
|
+
lines.push(` ${chalk.gray('Missing permission:')} ${chalk.white(perm)}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
309
317
|
/**
|
|
310
318
|
* @param {string[]} lines
|
|
311
319
|
* @param {Object} envelope
|
|
@@ -12,6 +12,7 @@ const path = require('path');
|
|
|
12
12
|
const readline = require('readline');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const { nodeFs } = require('../internal/node-fs');
|
|
15
|
+
const { formatSuccessLine } = require('./cli-layout-chalk');
|
|
15
16
|
|
|
16
17
|
const IPV4_RE = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/;
|
|
17
18
|
|
|
@@ -238,7 +239,7 @@ async function resolveHostsIpForInit(hostname, hostsIp, logger) {
|
|
|
238
239
|
async function appendHostsBlockOrPrintManual(hostsPath, block, line, logger) {
|
|
239
240
|
try {
|
|
240
241
|
await nodeFs().promises.appendFile(hostsPath, block, { encoding: 'utf8' });
|
|
241
|
-
logger.log(chalk.green(
|
|
242
|
+
logger.log(chalk.green(' ') + formatSuccessLine(`Updated ${hostsPath}`) + '\n');
|
|
242
243
|
} catch (e) {
|
|
243
244
|
if (e.code === 'EACCES' || e.code === 'EPERM') {
|
|
244
245
|
logger.log(chalk.yellow(` ✖ Could not write ${hostsPath} (permission denied).`));
|
|
@@ -261,7 +262,7 @@ async function appendHostsBlockOrPrintManual(hostsPath, block, line, logger) {
|
|
|
261
262
|
async function tryWriteHostsEntry(hostsPath, hostnames, ip, skipConfirm, logger) {
|
|
262
263
|
const missing = hostnames.filter((h) => !hostsFileHasHostname(hostsPath, h));
|
|
263
264
|
if (missing.length === 0) {
|
|
264
|
-
logger.log(chalk.green(
|
|
265
|
+
logger.log(chalk.green(' ') + formatSuccessLine(`Required hostnames are already listed in ${hostsPath}. Nothing to do.`) + '\n');
|
|
265
266
|
return;
|
|
266
267
|
}
|
|
267
268
|
const line = `${ip} ${missing.join(' ')}`;
|
|
@@ -6,6 +6,7 @@ const chalk = require('chalk');
|
|
|
6
6
|
const config = require('../core/config');
|
|
7
7
|
const logger = require('./logger');
|
|
8
8
|
const { ensureDevSshConfigBlock } = require('./dev-ssh-config-helper');
|
|
9
|
+
const { successGlyph } = require('./cli-layout-chalk');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Hostname from Builder Server URL (for sync-ssh-host fallback).
|
|
@@ -46,7 +47,7 @@ async function mergeDevSshConfigAfterInit(baseUrl, devId) {
|
|
|
46
47
|
);
|
|
47
48
|
} else {
|
|
48
49
|
logger.log(
|
|
49
|
-
chalk.green('
|
|
50
|
+
`${chalk.green(' ')}${successGlyph()}${chalk.green(' SSH config updated: ')}` +
|
|
50
51
|
chalk.cyan(`Host ${res.hostAlias}`) +
|
|
51
52
|
chalk.gray(` → ${res.configPath}`)
|
|
52
53
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
1
|
+
const { formatSuccessLine, headerKeyValue, formatWarningLine } = require('./cli-test-layout-chalk');
|
|
2
2
|
/**
|
|
3
3
|
* Docker Build Utilities
|
|
4
4
|
*
|
|
@@ -177,7 +177,6 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
177
177
|
const spinner = ora({ text: 'Starting Docker build...', spinner: 'dots' }).start();
|
|
178
178
|
const fsSync = require('fs');
|
|
179
179
|
const path = require('path');
|
|
180
|
-
const { getRemoteDockerEnv } = require('./remote-docker-env');
|
|
181
180
|
dockerfilePath = path.resolve(dockerfilePath);
|
|
182
181
|
contextPath = path.resolve(contextPath);
|
|
183
182
|
|
|
@@ -196,7 +195,8 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
|
199
|
-
const
|
|
198
|
+
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
199
|
+
const dockerCliEnv = isTest ? { ...process.env } : await getDockerExecEnv();
|
|
200
200
|
const resolvedBuildArgs = buildArgs && typeof buildArgs === 'object' ? buildArgs : {};
|
|
201
201
|
return new Promise((resolve, reject) => {
|
|
202
202
|
runDockerBuildProcess({
|
|
@@ -207,7 +207,7 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
207
207
|
spinner,
|
|
208
208
|
resolve,
|
|
209
209
|
reject,
|
|
210
|
-
env:
|
|
210
|
+
env: dockerCliEnv,
|
|
211
211
|
buildArgs: resolvedBuildArgs,
|
|
212
212
|
noCache
|
|
213
213
|
});
|
|
@@ -258,14 +258,20 @@ async function executeBuild(imageName, dockerfilePath, contextPath, tag, options
|
|
|
258
258
|
*/
|
|
259
259
|
async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, options) {
|
|
260
260
|
const logger = require('../utils/logger');
|
|
261
|
-
const chalk = require('chalk');
|
|
262
261
|
|
|
263
|
-
logger.log(
|
|
264
|
-
logger.log(
|
|
262
|
+
logger.log(headerKeyValue('Dockerfile:', dockerfilePath));
|
|
263
|
+
logger.log(headerKeyValue('Build context:', contextPath));
|
|
265
264
|
|
|
266
265
|
await executeBuild(effectiveImageName, dockerfilePath, contextPath, tag, options);
|
|
267
266
|
|
|
268
|
-
|
|
267
|
+
const wantCompatTag =
|
|
268
|
+
effectiveImageName !== imageName &&
|
|
269
|
+
options &&
|
|
270
|
+
options.base === true;
|
|
271
|
+
if (!wantCompatTag) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
269
275
|
try {
|
|
270
276
|
const { promisify } = require('util');
|
|
271
277
|
const { exec } = require('child_process');
|
|
@@ -275,7 +281,9 @@ async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfi
|
|
|
275
281
|
await run(`docker tag ${effectiveImageName}:${tag} ${imageName}:${tag}`, { env });
|
|
276
282
|
logger.log(formatSuccessLine(`Tagged image: ${imageName}:${tag}`));
|
|
277
283
|
} catch (err) {
|
|
278
|
-
logger.log(
|
|
284
|
+
logger.log(
|
|
285
|
+
formatWarningLine(`Could not create compatibility tag ${imageName}:${tag} - ${err.message}`)
|
|
286
|
+
);
|
|
279
287
|
}
|
|
280
288
|
}
|
|
281
289
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether `aifabrix run --reload` can bind-mount workspace without Mutagen.
|
|
3
|
+
* Mutagen is only needed when the CLI filesystem and the Docker engine host differ.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Co-located Docker detection for reload mounts
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} host
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function normalizeHost(host) {
|
|
19
|
+
return String(host || '')
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/^\[|\]$/g, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} host
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isLocalLoopbackHost(host) {
|
|
30
|
+
const h = normalizeHost(host);
|
|
31
|
+
return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0:0:0:0:0:0:0:1';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} rest - after "tcp://"
|
|
36
|
+
* @returns {string|null}
|
|
37
|
+
*/
|
|
38
|
+
function tcpHostFromRest(rest) {
|
|
39
|
+
if (rest.startsWith('[')) {
|
|
40
|
+
const end = rest.indexOf(']');
|
|
41
|
+
if (end === -1) return null;
|
|
42
|
+
return normalizeHost(rest.slice(1, end));
|
|
43
|
+
}
|
|
44
|
+
const hostPort = rest.split('/')[0];
|
|
45
|
+
const colonIdx = hostPort.lastIndexOf(':');
|
|
46
|
+
if (colonIdx === -1) {
|
|
47
|
+
return normalizeHost(hostPort);
|
|
48
|
+
}
|
|
49
|
+
const maybePort = hostPort.slice(colonIdx + 1);
|
|
50
|
+
if (/^\d+$/.test(maybePort)) {
|
|
51
|
+
return normalizeHost(hostPort.slice(0, colonIdx));
|
|
52
|
+
}
|
|
53
|
+
return normalizeHost(hostPort);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract host from docker-endpoint (tcp, optional URL form).
|
|
58
|
+
* @param {string} endpoint
|
|
59
|
+
* @returns {string|null} normalized host, or null for unix socket paths / empty
|
|
60
|
+
*/
|
|
61
|
+
function extractHostFromDockerEndpoint(endpoint) {
|
|
62
|
+
const s = String(endpoint || '').trim();
|
|
63
|
+
if (!s) return null;
|
|
64
|
+
const lower = s.toLowerCase();
|
|
65
|
+
if (lower.startsWith('unix:')) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (lower.startsWith('tcp://')) {
|
|
69
|
+
return tcpHostFromRest(s.slice(6));
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const u = new URL(s.includes('://') ? s : `tcp://${s}`);
|
|
73
|
+
return normalizeHost(u.hostname);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* sync-ssh-host localhost check (same rules as run.js isLocalhostHost for reload gate).
|
|
81
|
+
* @param {string} host
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
function isLocalhostSyncSshHost(host) {
|
|
85
|
+
if (!host || typeof host !== 'string') return false;
|
|
86
|
+
const h = host.trim().toLowerCase();
|
|
87
|
+
return h === 'localhost' || h === '127.0.0.1';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* True when docker API host is this machine (first label or FQDN match).
|
|
92
|
+
* @param {string} dockerHost - from extractHostFromDockerEndpoint
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
function hostnameMatchesDockerHost(dockerHost) {
|
|
96
|
+
const h = normalizeHost(dockerHost);
|
|
97
|
+
if (!h) return true;
|
|
98
|
+
if (isLocalLoopbackHost(h)) return true;
|
|
99
|
+
const hn = normalizeHost(os.hostname());
|
|
100
|
+
if (h === hn) return true;
|
|
101
|
+
const hnShort = hn.split('.')[0];
|
|
102
|
+
const hShort = h.split('.')[0];
|
|
103
|
+
if (h === hnShort || hn === hShort) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* When true, use a direct bind mount for --reload; Mutagen is not required.
|
|
109
|
+
* @param {string|null|undefined} dockerEndpoint - config `docker-endpoint`
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
function isReloadBindMountOnEngineHost(dockerEndpoint) {
|
|
113
|
+
const e = String(dockerEndpoint || '').trim();
|
|
114
|
+
if (!e) return true;
|
|
115
|
+
if (e.toLowerCase().startsWith('unix:')) return true;
|
|
116
|
+
const host = extractHostFromDockerEndpoint(e);
|
|
117
|
+
if (host === null) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return hostnameMatchesDockerHost(host);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
extractHostFromDockerEndpoint,
|
|
125
|
+
isReloadBindMountOnEngineHost,
|
|
126
|
+
isLocalhostSyncSshHost
|
|
127
|
+
};
|
|
@@ -15,6 +15,72 @@ const path = require('path');
|
|
|
15
15
|
const handlebars = require('handlebars');
|
|
16
16
|
const { getProjectRoot } = require('./paths');
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Extracts secret keys from integration/<appName>/env.template when present.
|
|
20
|
+
* Looks for values like `kv://systemKey/apiKey` and returns that key for
|
|
21
|
+
* `aifabrix secret set <key> <value>`.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} args
|
|
24
|
+
* @param {string} args.projectRoot
|
|
25
|
+
* @param {string} args.appName
|
|
26
|
+
* @returns {Array<{path: string, description: string}>}
|
|
27
|
+
*/
|
|
28
|
+
function extractSecretPathsFromEnvTemplate({ projectRoot, appName }) {
|
|
29
|
+
if (!projectRoot || !appName) return [];
|
|
30
|
+
|
|
31
|
+
const envTemplatePath = path.join(projectRoot, 'integration', appName, 'env.template');
|
|
32
|
+
if (!fs.existsSync(envTemplatePath)) return [];
|
|
33
|
+
|
|
34
|
+
const raw = _tryReadTextOrNull(envTemplatePath);
|
|
35
|
+
if (raw === null) return [];
|
|
36
|
+
if (typeof raw !== 'string') return [];
|
|
37
|
+
|
|
38
|
+
function parseSecretKey(value) {
|
|
39
|
+
if (!value || typeof value !== 'string') return null;
|
|
40
|
+
const kvIdx = value.indexOf('kv://');
|
|
41
|
+
if (kvIdx === -1) return null;
|
|
42
|
+
const after = value.slice(kvIdx + 'kv://'.length);
|
|
43
|
+
const key = after.split(/[ \t#]/)[0]?.trim();
|
|
44
|
+
return key || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseLine(line) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (!trimmed || trimmed.startsWith('#')) return null;
|
|
50
|
+
const eqIdx = trimmed.indexOf('=');
|
|
51
|
+
if (eqIdx <= 0) return null;
|
|
52
|
+
const varName = trimmed.slice(0, eqIdx).trim();
|
|
53
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
54
|
+
const key = parseSecretKey(value);
|
|
55
|
+
if (!key) return null;
|
|
56
|
+
return { varName: varName || 'Secret', key };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const out = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
|
|
62
|
+
for (const line of raw.split('\n')) {
|
|
63
|
+
const parsed = parseLine(line);
|
|
64
|
+
if (!parsed) continue;
|
|
65
|
+
const dedupeKey = `${parsed.varName}::${parsed.key}`;
|
|
66
|
+
if (seen.has(dedupeKey)) continue;
|
|
67
|
+
seen.add(dedupeKey);
|
|
68
|
+
out.push({ path: parsed.key, description: parsed.varName });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _tryReadTextOrNull(p) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.readFileSync(p, 'utf8');
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// Some Jest suites partially mock fs.existsSync; treat missing files as absent templates.
|
|
79
|
+
if (e && e.code === 'ENOENT') return null;
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
18
84
|
/**
|
|
19
85
|
* Formats a display name from a key
|
|
20
86
|
* @param {string} key - System or app key
|
|
@@ -179,7 +245,9 @@ function buildExternalReadmeContext(params = {}) {
|
|
|
179
245
|
const normalizedExt = fileExt && fileExt.startsWith('.') ? fileExt : `.${fileExt || 'json'}`;
|
|
180
246
|
const datasources = normalizeDatasources(params.datasources, systemKey, fileExt);
|
|
181
247
|
const authType = params.authType || params.authentication?.type || params.authentication?.method || params.authentication?.authType;
|
|
182
|
-
const
|
|
248
|
+
const projectRoot = getProjectRoot();
|
|
249
|
+
const secretPathsFromEnv = extractSecretPathsFromEnvTemplate({ projectRoot, appName });
|
|
250
|
+
const secretPaths = secretPathsFromEnv.length > 0 ? secretPathsFromEnv : buildSecretPaths(systemKey, authType);
|
|
183
251
|
const rbacOptionalFile = rbacOptionalFilename(normalizedExt);
|
|
184
252
|
|
|
185
253
|
return {
|
|
@@ -238,5 +306,6 @@ module.exports = {
|
|
|
238
306
|
buildExternalReadmeContext,
|
|
239
307
|
generateExternalReadmeContent,
|
|
240
308
|
wrapPlainTextForMarkdown,
|
|
241
|
-
collapseConsecutiveBlankLines
|
|
309
|
+
collapseConsecutiveBlankLines,
|
|
310
|
+
extractSecretPathsFromEnvTemplate
|
|
242
311
|
};
|