@aifabrix/builder 2.42.0 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/aifabrix.js +1 -1
- package/integration/hubspot-test/README.md +126 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +5 -5
- package/integration/hubspot-test/env.template +4 -0
- package/integration/{hubspot/hubspot-datasource-company.json → hubspot-test/hubspot-test-datasource-company.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-contact.json → hubspot-test/hubspot-test-datasource-contact.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-deal.json → hubspot-test/hubspot-test-datasource-deal.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-users.json → hubspot-test/hubspot-test-datasource-users.json} +3 -2
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +198 -21
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test.js +102 -59
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/lib/api/external-test.api.js +1 -1
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/api/wizard.api.js +2 -1
- package/lib/app/index.js +2 -2
- package/lib/app/prompts.js +2 -2
- package/lib/app/readme.js +3 -1
- package/lib/app/register.js +3 -1
- package/lib/app/rotate-secret.js +3 -0
- package/lib/cli/setup-app.js +5 -5
- package/lib/cli/setup-auth.js +19 -11
- package/lib/cli/setup-dev.js +62 -32
- package/lib/cli/setup-environment.js +6 -21
- package/lib/cli/setup-infra.js +13 -0
- package/lib/cli/setup-secrets.js +45 -6
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +12 -0
- package/lib/commands/auth-config.js +25 -19
- package/lib/commands/datasource.js +46 -1
- package/lib/commands/dev-init.js +1 -1
- package/lib/commands/repair-env-template.js +14 -8
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +108 -31
- package/lib/commands/secrets-remove.js +1 -1
- package/lib/commands/secrets-set.js +6 -0
- package/lib/commands/secrets-validate.js +17 -4
- package/lib/commands/service-user.js +231 -2
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/up-dataplane.js +91 -7
- package/lib/commands/wizard-core-helpers.js +5 -2
- package/lib/commands/wizard-core.js +2 -1
- package/lib/commands/wizard-headless.js +6 -1
- package/lib/commands/wizard.js +13 -6
- package/lib/core/admin-secrets.js +2 -0
- package/lib/core/config.js +7 -5
- package/lib/core/ensure-encryption-key.js +1 -3
- package/lib/core/secrets.js +32 -9
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +157 -0
- package/lib/datasource/field-reference-validator.js +74 -36
- package/lib/datasource/log-viewer.js +221 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +11 -20
- package/lib/datasource/test-integration.js +42 -22
- package/lib/datasource/validate.js +5 -2
- package/lib/external-system/download-helpers.js +3 -1
- package/lib/external-system/generator.js +12 -8
- package/lib/external-system/test-system-level.js +1 -1
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external-schema-utils.js +3 -1
- package/lib/generator/external.js +7 -7
- package/lib/generator/helpers.js +13 -9
- package/lib/generator/index.js +4 -4
- package/lib/generator/split.js +45 -10
- package/lib/generator/wizard-prompts-secondary.js +39 -7
- package/lib/generator/wizard-readme.js +4 -1
- package/lib/generator/wizard.js +68 -53
- package/lib/infrastructure/helpers.js +50 -35
- package/lib/infrastructure/index.js +39 -23
- package/lib/schema/env-config.yaml +19 -2
- package/lib/schema/external-datasource.schema.json +11 -1
- package/lib/schema/wizard-config.schema.json +7 -1
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/config-paths.js +48 -4
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/env-map.js +7 -3
- package/lib/utils/error-formatter.js +37 -0
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-readme.js +33 -1
- package/lib/utils/external-system-display.js +43 -0
- package/lib/utils/external-system-validators.js +2 -2
- package/lib/utils/help-builder.js +3 -5
- package/lib/utils/local-secrets.js +26 -3
- package/lib/utils/paths.js +2 -1
- package/lib/utils/secrets-generator.js +2 -2
- package/lib/utils/secrets-utils.js +4 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/token-manager.js +36 -3
- package/lib/utils/yaml-preserve.js +59 -1
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +8 -0
- package/lib/validation/validate.js +8 -0
- package/lib/validation/validator.js +10 -13
- package/package.json +6 -2
- package/templates/applications/dataplane/env.template +5 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -2
- package/templates/external-system/README.md.hbs +18 -5
- package/templates/external-system/env.template.hbs +22 -0
- package/integration/hubspot/README.md +0 -100
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +0 -0
package/lib/cli/setup-utility.js
CHANGED
|
@@ -163,6 +163,7 @@ function setupRepairCommand(program) {
|
|
|
163
163
|
program.command('repair <app>')
|
|
164
164
|
.description('Repair external integration config: fix drift (file lists, app key, datasource alignment, rbac, manifest)')
|
|
165
165
|
.option('--auth <method>', 'Set authentication method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none); updates system file and env.template')
|
|
166
|
+
.option('--doc', 'Regenerate README.md from deployment manifest')
|
|
166
167
|
.option('--dry-run', 'Report changes only; do not write')
|
|
167
168
|
.option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist')
|
|
168
169
|
.option('--expose', 'Set exposed.attributes on each datasource to all fieldMappings.attributes keys')
|
|
@@ -174,9 +175,18 @@ function setupRepairCommand(program) {
|
|
|
174
175
|
const { detectAppType } = require('../utils/paths');
|
|
175
176
|
const { appPath } = await detectAppType(appName);
|
|
176
177
|
logOfflinePathWhenType(appPath);
|
|
178
|
+
let format = 'yaml';
|
|
179
|
+
try {
|
|
180
|
+
const config = require('../core/config');
|
|
181
|
+
format = (await config.getFormat()) || format;
|
|
182
|
+
} catch (_) {
|
|
183
|
+
// use default yaml when config unavailable
|
|
184
|
+
}
|
|
177
185
|
const result = await repairExternalIntegration(appName, {
|
|
178
186
|
auth: options.auth,
|
|
187
|
+
doc: options.doc,
|
|
179
188
|
dryRun: options.dryRun,
|
|
189
|
+
format,
|
|
180
190
|
rbac: options.rbac,
|
|
181
191
|
expose: options.expose,
|
|
182
192
|
sync: options.sync,
|
|
@@ -187,6 +197,8 @@ function setupRepairCommand(program) {
|
|
|
187
197
|
result.changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
|
|
188
198
|
} else if (result.updated) {
|
|
189
199
|
logger.log(chalk.green('\n✓ Repaired external integration config.'));
|
|
200
|
+
} else if (result.readmeRegenerated) {
|
|
201
|
+
logger.log(chalk.green('\n✓ Regenerated README.md from deployment manifest.'));
|
|
190
202
|
} else {
|
|
191
203
|
logger.log(chalk.gray('No changes needed; config already matches files on disk.'));
|
|
192
204
|
}
|
|
@@ -19,34 +19,44 @@ const {
|
|
|
19
19
|
validateEnvironment,
|
|
20
20
|
checkUserLoggedIn
|
|
21
21
|
} = require('../utils/auth-config-validator');
|
|
22
|
+
const { getControllerUrlFromLoggedInUser } = require('../utils/controller-url');
|
|
22
23
|
const logger = require('../utils/logger');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Handle set-controller command
|
|
27
|
+
* Allows setting the default controller when no credentials are stored, or when already logged in to that controller.
|
|
28
|
+
* If credentials exist for a different controller, throws with a clear message.
|
|
29
|
+
*
|
|
26
30
|
* @async
|
|
27
31
|
* @function handleSetController
|
|
28
32
|
* @param {string} url - Controller URL to set
|
|
29
33
|
* @returns {Promise<void>}
|
|
30
|
-
* @throws {Error} If validation fails
|
|
34
|
+
* @throws {Error} If validation fails or credentials exist for another controller
|
|
31
35
|
*/
|
|
32
36
|
async function handleSetController(url) {
|
|
33
37
|
try {
|
|
34
|
-
// Validate URL format
|
|
35
38
|
validateControllerUrl(url);
|
|
39
|
+
const normalizedUrl = url.trim().replace(/\/+$/, '');
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
41
|
+
const loggedInControllerUrl = await getControllerUrlFromLoggedInUser();
|
|
42
|
+
if (!loggedInControllerUrl) {
|
|
43
|
+
// No stored credentials: allow setting controller so "aifabrix login" opens the right place
|
|
44
|
+
await setControllerUrl(url);
|
|
45
|
+
logger.log(chalk.green(`✓ Controller URL set to: ${url}`));
|
|
46
|
+
return;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const normalizedLoggedIn = loggedInControllerUrl.trim().replace(/\/+$/, '');
|
|
50
|
+
if (normalizedLoggedIn === normalizedUrl) {
|
|
51
|
+
await setControllerUrl(url);
|
|
52
|
+
logger.log(chalk.green(`✓ Controller URL set to: ${url}`));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
throw new Error(
|
|
57
|
+
`You have credentials for another controller (${loggedInControllerUrl}).\n` +
|
|
58
|
+
'To use a different controller either run "aifabrix login" with that controller, or run "aifabrix logout" first to clear credentials, then set the new controller with "aifabrix auth --set-controller <url>".'
|
|
59
|
+
);
|
|
50
60
|
} catch (error) {
|
|
51
61
|
logger.error(chalk.red(`✗ Failed to set controller URL: ${error.message}`));
|
|
52
62
|
throw error;
|
|
@@ -70,8 +80,7 @@ async function handleSetEnvironment(environment) {
|
|
|
70
80
|
const controllerUrl = await getControllerUrl();
|
|
71
81
|
if (!controllerUrl) {
|
|
72
82
|
throw new Error(
|
|
73
|
-
'No controller URL found in config
|
|
74
|
-
'Please run "aifabrix login" first to set the controller URL.'
|
|
83
|
+
'No controller URL found in config. Run "aifabrix login" first, or set the controller with "aifabrix auth --set-controller <url>".'
|
|
75
84
|
);
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -79,8 +88,7 @@ async function handleSetEnvironment(environment) {
|
|
|
79
88
|
const isLoggedIn = await checkUserLoggedIn(controllerUrl);
|
|
80
89
|
if (!isLoggedIn) {
|
|
81
90
|
throw new Error(
|
|
82
|
-
`You are not logged in to controller ${controllerUrl}
|
|
83
|
-
'Please run "aifabrix login" first to authenticate with this controller.'
|
|
91
|
+
`You are not logged in to controller ${controllerUrl}. Run "aifabrix login" first to authenticate.`
|
|
84
92
|
);
|
|
85
93
|
}
|
|
86
94
|
|
|
@@ -107,9 +115,7 @@ async function handleSetEnvironment(environment) {
|
|
|
107
115
|
async function handleAuthConfig(options) {
|
|
108
116
|
if (!options.setController && !options.setEnvironment) {
|
|
109
117
|
throw new Error(
|
|
110
|
-
'No action specified. Use
|
|
111
|
-
' --set-controller <url>\n' +
|
|
112
|
-
' --set-environment <env>'
|
|
118
|
+
'No action specified. Use "aifabrix auth --set-controller <url>" or "aifabrix auth --set-environment <env>".'
|
|
113
119
|
);
|
|
114
120
|
}
|
|
115
121
|
if (options.setController) {
|
|
@@ -17,6 +17,7 @@ const { compareDatasources } = require('../datasource/diff');
|
|
|
17
17
|
const { deployDatasource } = require('../datasource/deploy');
|
|
18
18
|
const { runDatasourceTestIntegration } = require('../datasource/test-integration');
|
|
19
19
|
const { runDatasourceTestE2E } = require('../datasource/test-e2e');
|
|
20
|
+
const { runLogViewer } = require('../datasource/log-viewer');
|
|
20
21
|
const { displayIntegrationTestResults, displayE2EResults } = require('../utils/external-system-display');
|
|
21
22
|
|
|
22
23
|
function setupDatasourceValidateCommand(datasource) {
|
|
@@ -81,9 +82,10 @@ function setupDatasourceUploadCommand(datasource) {
|
|
|
81
82
|
function setupDatasourceTestIntegrationCommand(datasource) {
|
|
82
83
|
datasource.command('test-integration <datasourceKey>')
|
|
83
84
|
.description('Run integration (config) test for one datasource via dataplane pipeline')
|
|
84
|
-
.option('-a, --app <appKey>', 'App key (
|
|
85
|
+
.option('-a, --app <appKey>', 'App key (optional: resolve from cwd or datasource key if single match)')
|
|
85
86
|
.option('-p, --payload <file>', 'Path to custom test payload file')
|
|
86
87
|
.option('-e, --env <env>', 'Environment: dev, tst, or pro')
|
|
88
|
+
.option('-v, --verbose', 'Show detailed step and validation output')
|
|
87
89
|
.option('--debug', 'Include debug output and write log to integration/<appKey>/logs/')
|
|
88
90
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
89
91
|
.action(async(datasourceKey, options) => {
|
|
@@ -144,18 +146,61 @@ function setupDatasourceTestE2ECommand(datasource) {
|
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
function setupDatasourceLogE2ECommand(datasource) {
|
|
150
|
+
datasource.command('log-e2e <datasourceKey>')
|
|
151
|
+
.description('Display latest or specified E2E test log in readable format')
|
|
152
|
+
.option('-a, --app <appKey>', 'App key (optional: resolve from cwd or datasource key if single match)')
|
|
153
|
+
.option('-f, --file <path>', 'Path to log file (default: latest in app logs folder)')
|
|
154
|
+
.action(async(datasourceKey, options) => {
|
|
155
|
+
try {
|
|
156
|
+
await runLogViewer(datasourceKey, {
|
|
157
|
+
app: options.app,
|
|
158
|
+
file: options.file,
|
|
159
|
+
logType: 'test-e2e'
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
logger.error(chalk.red('❌ log-e2e failed:'), error.message);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function setupDatasourceLogIntegrationCommand(datasource) {
|
|
169
|
+
datasource.command('log-integration <datasourceKey>')
|
|
170
|
+
.description('Display latest or specified integration test log in readable format')
|
|
171
|
+
.option('-a, --app <appKey>', 'App key (optional: resolve from cwd or datasource key if single match)')
|
|
172
|
+
.option('-f, --file <path>', 'Path to log file (default: latest in app logs folder)')
|
|
173
|
+
.action(async(datasourceKey, options) => {
|
|
174
|
+
try {
|
|
175
|
+
await runLogViewer(datasourceKey, {
|
|
176
|
+
app: options.app,
|
|
177
|
+
file: options.file,
|
|
178
|
+
logType: 'test-integration'
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.error(chalk.red('❌ log-integration failed:'), error.message);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
147
187
|
/**
|
|
148
188
|
* Setup datasource management commands
|
|
149
189
|
* @param {Command} program - Commander program instance
|
|
150
190
|
*/
|
|
151
191
|
function setupDatasourceCommands(program) {
|
|
152
192
|
const datasource = program.command('datasource').description('Manage external data sources');
|
|
193
|
+
if (typeof datasource.alias === 'function') {
|
|
194
|
+
datasource.alias('ds');
|
|
195
|
+
}
|
|
153
196
|
setupDatasourceValidateCommand(datasource);
|
|
154
197
|
setupDatasourceListCommand(datasource);
|
|
155
198
|
setupDatasourceDiffCommand(datasource);
|
|
156
199
|
setupDatasourceUploadCommand(datasource);
|
|
157
200
|
setupDatasourceTestIntegrationCommand(datasource);
|
|
158
201
|
setupDatasourceTestE2ECommand(datasource);
|
|
202
|
+
setupDatasourceLogE2ECommand(datasource);
|
|
203
|
+
setupDatasourceLogIntegrationCommand(datasource);
|
|
159
204
|
}
|
|
160
205
|
|
|
161
206
|
module.exports = { setupDatasourceCommands };
|
package/lib/commands/dev-init.js
CHANGED
|
@@ -341,7 +341,7 @@ async function runDevRefresh(options = {}) {
|
|
|
341
341
|
logger.log(chalk.blue('\n🔄 Fetching settings from Builder Server...\n'));
|
|
342
342
|
const settings = await devApi.getSettings(auth.serverUrl, clientCertPem, clientKeyPem || undefined);
|
|
343
343
|
await config.mergeRemoteSettings(settings);
|
|
344
|
-
logger.log(chalk.green('✓ Config updated from server. Run "aifabrix dev
|
|
344
|
+
logger.log(chalk.green('✓ Config updated from server. Run "aifabrix dev show" to verify.\n'));
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
module.exports = { runDevInit, runDevRefresh };
|
|
@@ -11,6 +11,7 @@ const path = require('path');
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const { systemKeyToKvPrefix, kvEnvKeyToPath, securityKeyToVar } = require('../utils/credential-secrets-env');
|
|
13
13
|
const { extractEnvTemplate } = require('../generator/split');
|
|
14
|
+
const { generateExternalEnvTemplateContent } = require('../utils/external-env-template');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Normalizes a keyvault config entry to canonical KV_* name and path-style value.
|
|
@@ -126,20 +127,25 @@ function buildExpectedByKey(effective) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
|
-
* Creates env.template when missing
|
|
130
|
+
* Creates env.template when missing using the Handlebars template (Authentication + Configuration sections).
|
|
130
131
|
* @param {string} envPath - Path to env.template
|
|
131
|
-
* @param {Map<string, string>} expectedByKey - Expected key->line map
|
|
132
|
+
* @param {Map<string, string>} expectedByKey - Expected key->line map (fallback when no systemParsed)
|
|
132
133
|
* @param {boolean} dryRun - If true, do not write
|
|
133
134
|
* @param {string[]} changes - Array to append to
|
|
135
|
+
* @param {Object} [systemParsed] - Parsed system config for template context (preferred)
|
|
134
136
|
* @returns {boolean}
|
|
135
137
|
*/
|
|
136
|
-
function createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes) {
|
|
138
|
+
function createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes, systemParsed) {
|
|
137
139
|
if (fs.existsSync(envPath)) return false;
|
|
138
|
-
|
|
139
|
-
const content =
|
|
140
|
-
|
|
140
|
+
if (expectedByKey.size === 0) return false;
|
|
141
|
+
const content = systemParsed
|
|
142
|
+
? generateExternalEnvTemplateContent(systemParsed)
|
|
143
|
+
: Array.from(expectedByKey.values()).join('\n');
|
|
144
|
+
if (!content || !content.trim()) return false;
|
|
145
|
+
const hasKeyValueLine = /^[A-Z_][A-Z0-9_]*=/m.test(content);
|
|
146
|
+
if (!hasKeyValueLine) return false;
|
|
141
147
|
if (!dryRun) {
|
|
142
|
-
fs.writeFileSync(envPath, content + '\n', { mode: 0o644, encoding: 'utf8' });
|
|
148
|
+
fs.writeFileSync(envPath, content + (content.endsWith('\n') ? '' : '\n'), { mode: 0o644, encoding: 'utf8' });
|
|
143
149
|
}
|
|
144
150
|
changes.push('Created env.template from system configuration');
|
|
145
151
|
return true;
|
|
@@ -236,7 +242,7 @@ function repairEnvTemplate(appPath, systemParsed, systemKey, dryRun, changes) {
|
|
|
236
242
|
const expectedByKey = buildExpectedByKey(effective);
|
|
237
243
|
const envPath = path.join(appPath, 'env.template');
|
|
238
244
|
|
|
239
|
-
if (createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes)) {
|
|
245
|
+
if (createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes, systemParsed)) {
|
|
240
246
|
return true;
|
|
241
247
|
}
|
|
242
248
|
if (!fs.existsSync(envPath)) {
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
|
-
const yaml = require('js-yaml');
|
|
14
13
|
const chalk = require('chalk');
|
|
15
14
|
const logger = require('../utils/logger');
|
|
16
|
-
const { loadConfigFile } = require('../utils/config-format');
|
|
15
|
+
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
16
|
+
const { resolveRbacPath } = require('../utils/app-config-resolver');
|
|
17
17
|
|
|
18
18
|
const DEFAULT_CAPABILITIES = ['list', 'get', 'create', 'update', 'delete'];
|
|
19
19
|
|
|
@@ -55,26 +55,30 @@ function collectPermissionNames(appPath, datasourceFiles) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* Loads existing rbac or creates empty structure.
|
|
58
|
+
* Loads existing RBAC file (rbac.yaml, rbac.yml, or rbac.json) or creates empty structure.
|
|
59
|
+
* Uses extractRbacFromSystem when no file exists. New file path respects format (rbac.json when format is 'json').
|
|
60
|
+
*
|
|
59
61
|
* @param {string} appPath - Application path
|
|
60
62
|
* @param {Object} [systemParsed] - Parsed system for extractRbacFromSystem
|
|
61
63
|
* @param {Function} extractRbacFromSystem - (system) => rbac or null
|
|
62
|
-
* @
|
|
64
|
+
* @param {string} [format] - 'json' or 'yaml'; used only when creating a new file (default 'yaml')
|
|
65
|
+
* @returns {{ rbac: Object, rbacPath: string }} rbacPath is resolved path or path.join(appPath, 'rbac.{json|yaml}') for new file
|
|
63
66
|
*/
|
|
64
|
-
function loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem) {
|
|
65
|
-
const
|
|
66
|
-
const rbacYmlPath = path.join(appPath, 'rbac.yml');
|
|
67
|
+
function loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem, format) {
|
|
68
|
+
const resolvedPath = resolveRbacPath(appPath);
|
|
67
69
|
let rbac;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
let rbacPath;
|
|
71
|
+
if (resolvedPath) {
|
|
72
|
+
rbac = loadConfigFile(resolvedPath);
|
|
73
|
+
rbacPath = resolvedPath;
|
|
72
74
|
} else {
|
|
73
75
|
rbac = extractRbacFromSystem(systemParsed) || { roles: [], permissions: [] };
|
|
74
76
|
if (!Array.isArray(rbac.roles)) rbac.roles = [];
|
|
75
77
|
if (!Array.isArray(rbac.permissions)) rbac.permissions = [];
|
|
78
|
+
const ext = (format === 'json') ? 'rbac.json' : 'rbac.yaml';
|
|
79
|
+
rbacPath = path.join(appPath, ext);
|
|
76
80
|
}
|
|
77
|
-
return { rbac, rbacPath
|
|
81
|
+
return { rbac, rbacPath };
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
@@ -123,31 +127,33 @@ function ensureDefaultRoles(rbac, systemKey, displayName, changes) {
|
|
|
123
127
|
if (p.name && listGetPerms.includes(p.name) && !p.roles.includes(readerValue)) p.roles.push(readerValue);
|
|
124
128
|
if (!p.roles.includes(adminValue)) p.roles.push(adminValue);
|
|
125
129
|
}
|
|
126
|
-
changes.push('Added default Admin and Reader roles to rbac
|
|
130
|
+
changes.push('Added default Admin and Reader roles to rbac file');
|
|
127
131
|
return true;
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
/**
|
|
131
135
|
* Merges RBAC from datasources: ensures permission per resourceType:capability, adds Admin/Reader roles if none.
|
|
136
|
+
* When creating a new RBAC file, uses rbac.json if format is 'json', otherwise rbac.yaml.
|
|
137
|
+
*
|
|
132
138
|
* @param {string} appPath - Application path
|
|
133
139
|
* @param {Object} systemParsed - Parsed system (key, displayName)
|
|
134
140
|
* @param {string[]} datasourceFiles - Datasource file names
|
|
135
141
|
* @param {Function} extractRbacFromSystem - (system) => rbac or null
|
|
136
|
-
* @param {boolean}
|
|
137
|
-
* @param {string[]} changes - Array to append change descriptions to
|
|
142
|
+
* @param {{ format?: string, dryRun: boolean, changes: string[] }} options - format ('json'|'yaml'), dryRun, changes array
|
|
138
143
|
* @returns {boolean} True if rbac was updated (or would be in dry-run)
|
|
139
144
|
*/
|
|
140
|
-
function mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem,
|
|
145
|
+
function mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, options) {
|
|
146
|
+
const { format = 'yaml', dryRun, changes } = options;
|
|
147
|
+
const rbacFormat = format === 'json' ? 'json' : 'yaml';
|
|
141
148
|
const permissionNames = collectPermissionNames(appPath, datasourceFiles);
|
|
142
149
|
if (permissionNames.size === 0) return false;
|
|
143
150
|
const systemKey = systemParsed?.key || 'system';
|
|
144
151
|
const displayName = systemParsed?.displayName || systemKey;
|
|
145
|
-
const { rbac, rbacPath
|
|
152
|
+
const { rbac, rbacPath } = loadOrCreateRbac(appPath, systemParsed, extractRbacFromSystem, rbacFormat);
|
|
146
153
|
let updated = addMissingPermissions(rbac, permissionNames, changes);
|
|
147
154
|
updated = ensureDefaultRoles(rbac, systemKey, displayName, changes) || updated;
|
|
148
155
|
if (updated && !dryRun) {
|
|
149
|
-
|
|
150
|
-
fs.writeFileSync(outPath, yaml.dump(rbac, { indent: 2, lineWidth: -1 }), { mode: 0o644, encoding: 'utf8' });
|
|
156
|
+
writeConfigFile(rbacPath, rbac);
|
|
151
157
|
}
|
|
152
158
|
return updated;
|
|
153
159
|
}
|
package/lib/commands/repair.js
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const chalk = require('chalk');
|
|
19
|
-
const
|
|
20
|
-
const {
|
|
21
|
-
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
19
|
+
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
20
|
+
const { resolveApplicationConfigPath, resolveRbacPath } = require('../utils/app-config-resolver');
|
|
22
21
|
const { loadConfigFile, writeConfigFile, writeYamlPreservingComments, isYamlPath } = require('../utils/config-format');
|
|
23
22
|
const { systemKeyToKvPrefix, securityKeyToVar } = require('../utils/credential-secrets-env');
|
|
24
23
|
const logger = require('../utils/logger');
|
|
25
24
|
const generator = require('../generator');
|
|
26
25
|
const { repairEnvTemplate, normalizeSystemFileAuthAndConfig } = require('./repair-env-template');
|
|
26
|
+
const { generateReadmeFromDeployJson } = require('../generator/split-readme');
|
|
27
27
|
const { repairDatasourceFile } = require('./repair-datasource');
|
|
28
28
|
const { mergeRbacFromDatasources } = require('./repair-rbac');
|
|
29
29
|
const { discoverIntegrationFiles, buildEffectiveDatasourceFiles } = require('./repair-internal');
|
|
@@ -249,6 +249,11 @@ function normalizedAuthPartFromConfigName(name, systemKey) {
|
|
|
249
249
|
const rest = n.slice(kvPrefix.length);
|
|
250
250
|
return rest.toUpperCase().replace(/_/g, '');
|
|
251
251
|
}
|
|
252
|
+
// Old-style or other KV_* names: treat last segment as var (e.g. KV_HUBSPOT_CLIENTID → CLIENTID)
|
|
253
|
+
if (n.toUpperCase().startsWith('KV_')) {
|
|
254
|
+
const parts = n.split('_').filter(Boolean);
|
|
255
|
+
if (parts.length >= 2) return parts[parts.length - 1].toUpperCase();
|
|
256
|
+
}
|
|
252
257
|
return n.toUpperCase().replace(/_/g, '');
|
|
253
258
|
}
|
|
254
259
|
|
|
@@ -273,21 +278,20 @@ function removeAuthVarsFromConfiguration(systemParsed, systemKey, dryRun, change
|
|
|
273
278
|
return true;
|
|
274
279
|
}
|
|
275
280
|
|
|
276
|
-
function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dryRun, changes) {
|
|
277
|
-
|
|
278
|
-
const rbacYmlPath = path.join(appPath, 'rbac.yml');
|
|
279
|
-
if (fs.existsSync(rbacPath) || fs.existsSync(rbacYmlPath)) return false;
|
|
281
|
+
function createRbacFromSystemIfNeeded(appPath, systemFilePath, systemParsed, dryRun, changes, format) {
|
|
282
|
+
if (resolveRbacPath(appPath)) return false;
|
|
280
283
|
const rbacFromSystem = extractRbacFromSystem(systemParsed);
|
|
281
284
|
if (!rbacFromSystem) return false;
|
|
282
285
|
if (!dryRun) {
|
|
283
|
-
const
|
|
284
|
-
|
|
286
|
+
const rbacFormat = format === 'json' ? 'json' : 'yaml';
|
|
287
|
+
const defaultRbacPath = path.join(appPath, rbacFormat === 'json' ? 'rbac.json' : 'rbac.yaml');
|
|
288
|
+
writeConfigFile(defaultRbacPath, rbacFromSystem, rbacFormat);
|
|
285
289
|
delete systemParsed.roles;
|
|
286
290
|
delete systemParsed.permissions;
|
|
287
291
|
writeConfigFile(systemFilePath, systemParsed);
|
|
288
292
|
}
|
|
289
293
|
changes.push('Created rbac.yaml from system roles/permissions');
|
|
290
|
-
changes.push('Removed roles/permissions from system file (now in rbac
|
|
294
|
+
changes.push('Removed roles/permissions from system file (now in rbac file)');
|
|
291
295
|
return true;
|
|
292
296
|
}
|
|
293
297
|
|
|
@@ -338,6 +342,36 @@ async function regenerateManifest(appName, appPath, changes) {
|
|
|
338
342
|
}
|
|
339
343
|
}
|
|
340
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Regenerates README.md from deployment manifest when options.doc is set.
|
|
347
|
+
* @param {string} appName - Application name
|
|
348
|
+
* @param {string} appPath - Application path
|
|
349
|
+
* @param {Object} options - Options (doc, dryRun)
|
|
350
|
+
* @param {string[]} changes - Array to append change messages to
|
|
351
|
+
* @returns {Promise<boolean>} True if README was regenerated
|
|
352
|
+
*/
|
|
353
|
+
async function regenerateReadmeIfRequested(appName, appPath, options, changes) {
|
|
354
|
+
if (!options.doc) return false;
|
|
355
|
+
const deployJsonPath = getDeployJsonPath(appName, 'external', true);
|
|
356
|
+
if (!fs.existsSync(deployJsonPath) && !options.dryRun) {
|
|
357
|
+
await regenerateManifest(appName, appPath, changes);
|
|
358
|
+
}
|
|
359
|
+
if (!fs.existsSync(deployJsonPath)) return false;
|
|
360
|
+
try {
|
|
361
|
+
const deployment = JSON.parse(fs.readFileSync(deployJsonPath, 'utf8'));
|
|
362
|
+
const readmeContent = generateReadmeFromDeployJson(deployment);
|
|
363
|
+
const readmePath = path.join(appPath, 'README.md');
|
|
364
|
+
if (!options.dryRun) {
|
|
365
|
+
fs.writeFileSync(readmePath, readmeContent, { mode: 0o644, encoding: 'utf8' });
|
|
366
|
+
}
|
|
367
|
+
changes.push('Regenerated README.md from deployment manifest');
|
|
368
|
+
return true;
|
|
369
|
+
} catch (err) {
|
|
370
|
+
logger.log(chalk.yellow(`⚠ Could not regenerate README: ${err.message}`));
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
341
375
|
function persistChangesAndRegenerate(configPath, variables, appName, appPath, changes, originalYamlContent) {
|
|
342
376
|
if (originalYamlContent !== null && originalYamlContent !== undefined && typeof originalYamlContent === 'string' && isYamlPath(configPath)) {
|
|
343
377
|
writeYamlPreservingComments(configPath, originalYamlContent, variables);
|
|
@@ -351,6 +385,7 @@ function persistChangesAndRegenerate(configPath, variables, appName, appPath, ch
|
|
|
351
385
|
|
|
352
386
|
/**
|
|
353
387
|
* Apply --auth: replace system file authentication with canonical block for the given method.
|
|
388
|
+
* Preserves existing authentication.variables (e.g. baseUrl, tokenUrl) from the current system file.
|
|
354
389
|
* @param {Object} ctx - Context with systemParsed, systemKey, auth, dryRun, changes
|
|
355
390
|
* @returns {boolean} True if auth was replaced
|
|
356
391
|
*/
|
|
@@ -362,8 +397,18 @@ function applyAuthMethod(ctx) {
|
|
|
362
397
|
`Invalid --auth "${ctx.auth}". Allowed methods: ${ALLOWED_AUTH.join(', ')}`
|
|
363
398
|
);
|
|
364
399
|
}
|
|
400
|
+
const existingAuth = ctx.systemParsed.authentication || ctx.systemParsed.auth || {};
|
|
365
401
|
const { buildAuthenticationFromMethod } = require('../external-system/generator');
|
|
366
|
-
|
|
402
|
+
const newAuth = buildAuthenticationFromMethod(ctx.systemKey, method);
|
|
403
|
+
const existingVars = existingAuth.variables && typeof existingAuth.variables === 'object' ? existingAuth.variables : {};
|
|
404
|
+
const mergedVariables = { ...newAuth.variables, ...existingVars };
|
|
405
|
+
ctx.systemParsed.authentication = {
|
|
406
|
+
...newAuth,
|
|
407
|
+
variables: Object.keys(mergedVariables).length ? mergedVariables : newAuth.variables
|
|
408
|
+
};
|
|
409
|
+
if (existingAuth.displayName !== undefined) {
|
|
410
|
+
ctx.systemParsed.authentication.displayName = existingAuth.displayName;
|
|
411
|
+
}
|
|
367
412
|
ctx.changes.push(`Set authentication method to ${method}`);
|
|
368
413
|
return true;
|
|
369
414
|
}
|
|
@@ -399,7 +444,7 @@ function runRepairSteps(ctx) {
|
|
|
399
444
|
ctx.appPath, ctx.datasourceFiles, ctx.systemKey, ctx.dryRun, ctx.changes
|
|
400
445
|
);
|
|
401
446
|
const rbacFileCreated = createRbacFromSystemIfNeeded(
|
|
402
|
-
ctx.appPath, ctx.systemFilePath, ctx.systemParsed, ctx.dryRun, ctx.changes
|
|
447
|
+
ctx.appPath, ctx.systemFilePath, ctx.systemParsed, ctx.dryRun, ctx.changes, ctx.format
|
|
403
448
|
);
|
|
404
449
|
const envTemplateRepaired = repairEnvTemplate(
|
|
405
450
|
ctx.appPath, ctx.systemParsed, ctx.systemKey, ctx.dryRun, ctx.changes
|
|
@@ -420,7 +465,8 @@ function runRepairSteps(ctx) {
|
|
|
420
465
|
* @param {string} appName - Application/integration name
|
|
421
466
|
* @param {Object} [options] - Options
|
|
422
467
|
* @param {boolean} [options.dryRun] - If true, only report changes; do not write
|
|
423
|
-
* @
|
|
468
|
+
* @param {boolean} [options.doc] - If true, regenerate README.md from deployment manifest
|
|
469
|
+
* @returns {Promise<{ updated: boolean, changes: string[], systemFiles: string[], datasourceFiles: string[], appKeyFixed?: boolean, datasourceKeysFixed?: boolean, rbacFileCreated?: boolean, envTemplateRepaired?: boolean, manifestRegenerated?: boolean, readmeRegenerated?: boolean }>}
|
|
424
470
|
*/
|
|
425
471
|
/**
|
|
426
472
|
* Loads application config and discovers integration files; validates at least one system file exists.
|
|
@@ -442,7 +488,36 @@ function loadConfigAndDiscover(appPath, configPath) {
|
|
|
442
488
|
return { variables, originalYamlContent, systemFiles, datasourceFiles };
|
|
443
489
|
}
|
|
444
490
|
|
|
445
|
-
|
|
491
|
+
/**
|
|
492
|
+
* Builds the repair result object from steps and flags.
|
|
493
|
+
* @param {Object} steps - Result of runRepairSteps
|
|
494
|
+
* @param {boolean} anyUpdated - Whether any repair made changes
|
|
495
|
+
* @param {boolean} manifestRegenerated - Whether manifest was regenerated
|
|
496
|
+
* @param {boolean} readmeRegenerated - Whether README was regenerated
|
|
497
|
+
* @param {{ changes: string[], systemFiles: string[], datasourceFiles: string[] }} ctx - changes and file lists
|
|
498
|
+
* @returns {Object} Combined result object
|
|
499
|
+
*/
|
|
500
|
+
function buildRepairResult(steps, anyUpdated, manifestRegenerated, readmeRegenerated, ctx) {
|
|
501
|
+
return Object.assign(
|
|
502
|
+
{ updated: anyUpdated, changes: ctx.changes, systemFiles: ctx.systemFiles, datasourceFiles: ctx.datasourceFiles },
|
|
503
|
+
{
|
|
504
|
+
appKeyFixed: steps.appKeyFixed,
|
|
505
|
+
datasourceKeysFixed: steps.datasourceKeysFixed,
|
|
506
|
+
rbacFileCreated: steps.rbacFileCreated,
|
|
507
|
+
envTemplateRepaired: steps.envTemplateRepaired,
|
|
508
|
+
manifestRegenerated,
|
|
509
|
+
readmeRegenerated
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Validates repair inputs and resolves app path and config path.
|
|
516
|
+
* @param {string} appName - Application name
|
|
517
|
+
* @param {Object} options - Command options (auth)
|
|
518
|
+
* @returns {Promise<{ appPath: string, configPath: string, dryRun: boolean, authOption: string|undefined }>}
|
|
519
|
+
*/
|
|
520
|
+
async function validateAndResolveRepairPaths(appName, options) {
|
|
446
521
|
if (!appName || typeof appName !== 'string') throw new Error('App name is required');
|
|
447
522
|
const { dryRun = false, auth: authOption } = options;
|
|
448
523
|
if (authOption !== undefined && authOption !== null && typeof authOption !== 'string') {
|
|
@@ -452,6 +527,11 @@ async function repairExternalIntegration(appName, options = {}) {
|
|
|
452
527
|
if (!isExternal) throw new Error(`App '${appName}' is not an external integration`);
|
|
453
528
|
const configPath = resolveApplicationConfigPath(appPath);
|
|
454
529
|
if (!fs.existsSync(configPath)) throw new Error(`Application config not found: ${configPath}`);
|
|
530
|
+
return { appPath, configPath, dryRun, authOption };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function repairExternalIntegration(appName, options = {}) {
|
|
534
|
+
const { appPath, configPath, dryRun, authOption } = await validateAndResolveRepairPaths(appName, options);
|
|
455
535
|
|
|
456
536
|
const { variables, originalYamlContent, systemFiles, datasourceFiles: initialDatasourceFiles } = loadConfigAndDiscover(appPath, configPath);
|
|
457
537
|
const changes = [];
|
|
@@ -475,30 +555,27 @@ async function repairExternalIntegration(appName, options = {}) {
|
|
|
475
555
|
datasourceFiles,
|
|
476
556
|
dryRun,
|
|
477
557
|
changes,
|
|
478
|
-
auth: authOption
|
|
558
|
+
auth: authOption,
|
|
559
|
+
format: options.format === 'json' ? 'json' : 'yaml'
|
|
479
560
|
});
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
561
|
+
const datasourceRepairUpdated = runDatasourceRepairs(appPath, datasourceFiles, {
|
|
562
|
+
expose: Boolean(options.expose),
|
|
563
|
+
sync: Boolean(options.sync),
|
|
564
|
+
test: Boolean(options.test)
|
|
565
|
+
}, dryRun, changes);
|
|
483
566
|
const rbacMergeUpdated = options.rbac
|
|
484
|
-
? mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem,
|
|
567
|
+
? mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extractRbacFromSystem, {
|
|
568
|
+
format: options.format === 'json' ? 'json' : 'yaml',
|
|
569
|
+
dryRun,
|
|
570
|
+
changes
|
|
571
|
+
})
|
|
485
572
|
: false;
|
|
486
573
|
const anyUpdated = keysNormalized || steps.updated || datasourceRepairUpdated || rbacMergeUpdated;
|
|
487
574
|
const manifestRegenerated = (anyUpdated && !dryRun)
|
|
488
575
|
? await persistChangesAndRegenerate(configPath, variables, appName, appPath, changes, originalYamlContent)
|
|
489
576
|
: false;
|
|
490
|
-
|
|
491
|
-
return {
|
|
492
|
-
updated: anyUpdated,
|
|
493
|
-
changes,
|
|
494
|
-
systemFiles,
|
|
495
|
-
datasourceFiles,
|
|
496
|
-
appKeyFixed: steps.appKeyFixed,
|
|
497
|
-
datasourceKeysFixed: steps.datasourceKeysFixed,
|
|
498
|
-
rbacFileCreated: steps.rbacFileCreated,
|
|
499
|
-
envTemplateRepaired: steps.envTemplateRepaired,
|
|
500
|
-
manifestRegenerated
|
|
501
|
-
};
|
|
577
|
+
const readmeRegenerated = await regenerateReadmeIfRequested(appName, appPath, options, changes);
|
|
578
|
+
return buildRepairResult(steps, anyUpdated, manifestRegenerated, readmeRegenerated, { changes, systemFiles, datasourceFiles });
|
|
502
579
|
}
|
|
503
580
|
|
|
504
581
|
module.exports = {
|
|
@@ -33,7 +33,7 @@ function removeKeyFromFile(key, filePath) {
|
|
|
33
33
|
throw new Error(`Secret '${key}' not found.`);
|
|
34
34
|
}
|
|
35
35
|
delete data[key];
|
|
36
|
-
const yamlContent = yaml.dump(data, { indent: 2, lineWidth:
|
|
36
|
+
const yamlContent = yaml.dump(data, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false });
|
|
37
37
|
fs.writeFileSync(filePath, yamlContent, { mode: 0o600 });
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -60,6 +60,12 @@ async function handleSecretsSet(key, value, options) {
|
|
|
60
60
|
throw new Error('Secret key is required and must be a string');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
if (key.startsWith('kv://')) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'Secret key must not start with kv://. Use the key path without the prefix (e.g. my-app/clientSecret or hubspot/apiKey).'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
if (value === undefined || value === null || value === '') {
|
|
64
70
|
throw new Error('Secret value is required');
|
|
65
71
|
}
|