@aifabrix/builder 2.59.0 → 2.61.2
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 +14 -11
- package/docs/README.md +80 -0
- package/docs/builder-help/evidence-patterns.json +155 -0
- package/docs/builder-help/golden-examples/crm-company.json +30 -0
- package/docs/builder-help/golden-examples/crm-deal.json +29 -0
- package/docs/builder-help/golden-examples/document-storage-keyed-get.json +85 -0
- package/docs/builder-help/golden-examples/document-storage.json +30 -0
- package/docs/builder-help/golden-examples/meeting-transcript.json +29 -0
- package/docs/builder-help/golden-examples/repository-template.json +29 -0
- package/docs/builder-help/golden-examples/service-ticket.json +29 -0
- package/docs/builder-help/platform-roles.json +98 -0
- package/docs/builder-help/resource-type-catalog.json +402 -0
- package/lib/agent-kit/git-identity.js +180 -0
- package/lib/agent-kit/setup.js +3 -0
- package/lib/agent-kit/start.js +45 -1
- package/lib/api/configuration.api.js +131 -0
- package/lib/api/role-assistant-test-job.api.js +60 -0
- package/lib/api/system-secrets.api.js +72 -0
- package/lib/api/work-search.api.js +13 -6
- package/lib/app/deploy.js +8 -1
- package/lib/app/run-docker-fallback.js +6 -1
- package/lib/app/run-helpers.js +2 -1
- package/lib/app/run-parameter-sync.js +142 -0
- package/lib/app/show-display.js +1 -0
- package/lib/app/show-online.js +15 -0
- package/lib/build/docker-build-args.js +5 -2
- package/lib/build/index.js +3 -2
- package/lib/build/standard-docker-build.js +6 -3
- package/lib/cli/setup-app.js +19 -1
- package/lib/cli/setup-environment.js +156 -0
- package/lib/cli/setup-utility.js +24 -1
- package/lib/commands/datasource-capability-upsert-cli.js +112 -0
- package/lib/commands/datasource-capability.js +4 -2
- package/lib/commands/env-secret-context.js +113 -0
- package/lib/commands/env-secret-list.js +200 -0
- package/lib/commands/env-secret-push-confirm.js +54 -0
- package/lib/commands/env-secret-push-run.js +227 -0
- package/lib/commands/env-secret-push.js +168 -0
- package/lib/commands/repair-datasource-apply.js +2 -0
- package/lib/commands/repair-datasource-keyed-document.js +122 -0
- package/lib/commands/repair-datasource-run.js +1 -0
- package/lib/commands/role-assistant.js +7 -0
- package/lib/commands/setup-modes.js +1 -8
- package/lib/commands/setup-prompts.js +2 -180
- package/lib/commands/verify-operations-skip-e2e.js +25 -1
- package/lib/commands/verify-operations-steps.js +13 -1
- package/lib/commands/wizard-config-normalizer.js +7 -4
- package/lib/commands/wizard-core.js +5 -157
- package/lib/commands/wizard-file-saving.js +163 -0
- package/lib/core/env-platform-expand.js +97 -0
- package/lib/core/secrets-env-content.js +42 -4
- package/lib/core/secrets-env-write.js +10 -3
- package/lib/core/secrets-load.js +4 -2
- package/lib/datasource/binary-documents-validator.js +190 -0
- package/lib/datasource/capability/run-capability-upsert.js +202 -0
- package/lib/datasource/capability/upsert-ingredients.js +291 -0
- package/lib/datasource/capability/upsert-operations.js +138 -0
- package/lib/datasource/capability/upsert-test-scaffold.js +189 -0
- package/lib/datasource/validate.js +12 -5
- package/lib/deployment/installation/azure-infra-stage.js +3 -1
- package/lib/deployment/installation/infra-catalog.js +2 -5
- package/lib/generator/builders.js +17 -0
- package/lib/generator/helpers.js +23 -2
- package/lib/generator/index.js +13 -6
- package/lib/lifecycle/product-model.js +4 -3
- package/lib/lifecycle/report-display.js +3 -2
- package/lib/parameters/infra-parameter-catalog.js +1 -1
- package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +1 -1
- package/lib/programmatic/builder-help-governance.js +1 -1
- package/lib/programmatic/builder-help.js +1 -1
- package/lib/role-assistant/test-cases-search.js +3 -0
- package/lib/role-assistant/test-job-runner.js +125 -0
- package/lib/role-assistant/test-runner-search.js +44 -2
- package/lib/role-assistant/test-runner-workhub-answers.js +4 -1
- package/lib/role-assistant/test-runner-workhub-missing-fields.js +42 -0
- package/lib/role-assistant/test-runner-workhub-wait-stop.js +94 -0
- package/lib/role-assistant/test-runner-workhub.js +28 -19
- package/lib/schema/application-schema.json +205 -2
- package/lib/schema/external-datasource.schema.json +23 -3
- package/lib/schema/infra-parameter.schema.json +139 -33
- package/lib/schema/infra.parameter.yaml +416 -66
- package/lib/schema/infrastructure-schema.json +10 -35
- package/lib/utils/compose-generate-docker-compose.js +16 -9
- package/lib/utils/datasource-binary-evidence.js +92 -0
- package/lib/utils/datasource-test-run-capability-scope.js +44 -1
- package/lib/utils/datasource-test-run-debug-display.js +2 -0
- package/lib/utils/datasource-test-run-display.js +8 -2
- package/lib/utils/datasource-test-run-issue-guidance.js +176 -0
- package/lib/utils/datasource-test-run-tty-log.js +2 -0
- package/lib/utils/docker-build.js +29 -8
- package/lib/utils/docker-manifest-public-port.js +36 -0
- package/lib/utils/env-copy.js +11 -10
- package/lib/utils/external-system-system-test-tty.js +3 -2
- package/lib/utils/image-tags.js +2 -2
- package/lib/utils/platform-kv-ref.js +1 -1
- package/lib/utils/platform-resolution.js +226 -0
- package/lib/utils/prepare-local-data-mount.js +58 -0
- package/lib/utils/resolve-docker-image-ref.js +8 -3
- package/lib/utils/secrets-helpers.js +0 -1
- package/lib/utils/system-secret-mapping.js +125 -0
- package/lib/utils/test-log-writer.js +2 -1
- package/lib/validation/external-manifest-validator.js +5 -0
- package/lib/validation/openapi-contract-surface-validator.js +3 -1
- package/lib/validation/validate-external-file.js +5 -1
- package/package.json +5 -4
- package/templates/README.md +2 -1
- package/templates/agent-kit/agent-kit.yaml +4 -1
- package/templates/agent-kit/instructions/AGENTKIT.md +1 -1
- package/templates/agent-kit/instructions/root.AGENTS.md +2 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +35 -2
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/scripts/delivery-verdict.js +176 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +6 -2
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +28 -2
- package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +28 -1
- package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
- package/templates/agent-kit/skills/shared/feedback.md +31 -0
- package/templates/agent-kit/skills/shared/hosts.md +13 -3
- package/templates/agent-kit/skills/shared/interaction.md +83 -0
- package/templates/agent-kit/skills/shared/status.md +76 -0
- package/templates/agent-kit/workspace/BUILDER_IMPROVEMENT_FINDINGS.md +27 -0
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/builder-api/env.template +5 -1
- package/templates/applications/dataplane/application.yaml +30 -2
- package/templates/applications/dataplane/env.template +36 -5
- package/templates/applications/keycloak/application.yaml +6 -1
- package/templates/applications/miso-controller/application.yaml +96 -1
- package/templates/applications/miso-controller/env.template +63 -37
- package/templates/applications/miso-controller/rbac.yaml +17 -0
- package/templates/external-system/external-datasource.yaml.hbs +7 -1
- package/templates/marketplace/main.json +73 -225
- package/templates/python/Dockerfile.hbs +2 -0
- package/templates/python/docker-compose.hbs +1 -1
- package/templates/typescript/Dockerfile.hbs +2 -0
- package/templates/typescript/docker-compose.hbs +12 -0
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -34
- /package/{lib/programmatic/help-content → docs/builder-help/content}/channel-onboarding.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/cip-overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/connected-system-ui.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/dimensions-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/enterprise-sync-fabrix.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/subscription-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/workflow.md +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirmation gate for bulk installation-secret writes.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Prevent accidental env secret bulk pushes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const { formatWarningLine } = require('../utils/cli-test-layout-chalk');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} question - Prompt text
|
|
15
|
+
* @param {Object} [streams] - Injectable input/output streams
|
|
16
|
+
* @returns {Promise<string>} User response
|
|
17
|
+
*/
|
|
18
|
+
function promptLine(question, streams = {}) {
|
|
19
|
+
const input = streams.input || process.stdin;
|
|
20
|
+
const output = streams.output || process.stdout;
|
|
21
|
+
const rl = readline.createInterface({ input, output });
|
|
22
|
+
return new Promise(resolve => {
|
|
23
|
+
rl.question(question, answer => {
|
|
24
|
+
rl.close();
|
|
25
|
+
resolve(String(answer || '').trim());
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Require explicit approval only when every mapped secret would be written.
|
|
32
|
+
* @param {Object} ctx - Resolved environment context
|
|
33
|
+
* @param {Object} options - CLI options
|
|
34
|
+
* @param {Object} [deps] - Injectable prompt and TTY state
|
|
35
|
+
* @returns {Promise<boolean>} True when execution may continue
|
|
36
|
+
*/
|
|
37
|
+
async function confirmBulkSecretPush(ctx, options = {}, deps = {}) {
|
|
38
|
+
if (options.key || options.dryRun || options.yes) return true;
|
|
39
|
+
const isTTY = deps.isTTY === undefined
|
|
40
|
+
? Boolean(process.stdin.isTTY && process.stdout.isTTY) : deps.isTTY;
|
|
41
|
+
if (!isTTY) {
|
|
42
|
+
throw new Error('Bulk configuration push requires --yes in non-interactive or AI usage');
|
|
43
|
+
}
|
|
44
|
+
logger.log(formatWarningLine(
|
|
45
|
+
`Push all declared configuration from the .env for ${options.app} to ${ctx.envKey}?`
|
|
46
|
+
));
|
|
47
|
+
const prompt = deps.prompt || promptLine;
|
|
48
|
+
const answer = await prompt('Type "yes" to confirm (anything else cancels): ');
|
|
49
|
+
if (String(answer).trim().toLowerCase() === 'yes') return true;
|
|
50
|
+
logger.log('Cancelled. No configuration values were pushed.');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { promptLine, confirmBulkSecretPush };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration and reporting for `aifabrix env secret push`.
|
|
3
|
+
*
|
|
4
|
+
* Reporting is the security-sensitive half: every line names a variable, a catalog key and
|
|
5
|
+
* an outcome, and there is no branch that can print a value. Kept separate from the row
|
|
6
|
+
* mechanics so the redaction rule is easy to read in one place.
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview env secret push: run, report, exit code
|
|
9
|
+
* @author AI Fabrix Team
|
|
10
|
+
* @version 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
const {
|
|
18
|
+
OUTCOME, readEnvFile, pushRow
|
|
19
|
+
} = require('./env-secret-push');
|
|
20
|
+
const { parseEnvAssignments } = require('../utils/system-secret-mapping');
|
|
21
|
+
const {
|
|
22
|
+
listEnvironmentConfiguration,
|
|
23
|
+
putEnvironmentConfiguration
|
|
24
|
+
} = require('../api/configuration.api');
|
|
25
|
+
const { putSystemSecret } = require('../api/system-secrets.api');
|
|
26
|
+
|
|
27
|
+
const GLYPH = Object.freeze({ ok: '✔', fail: '✖', skip: '·' });
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} row - Reported row
|
|
31
|
+
* @returns {string} One aligned line naming the variable, key and outcome
|
|
32
|
+
*/
|
|
33
|
+
function formatRow(row, widths = { name: 34, key: 44 }) {
|
|
34
|
+
const glyph = row.outcome === OUTCOME.updated ? chalk.green(GLYPH.ok)
|
|
35
|
+
: row.outcome === OUTCOME.skipped ? chalk.gray(GLYPH.skip)
|
|
36
|
+
: chalk.red(GLYPH.fail);
|
|
37
|
+
const name = String(row.name).padEnd(widths.name);
|
|
38
|
+
const key = String(row.catalogKey || '—').padEnd(widths.key);
|
|
39
|
+
const detail = row.reason ? `${row.outcome} (${row.reason})` : row.outcome;
|
|
40
|
+
return ` ${name}-> ${key}${glyph} ${detail}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Keep variable and declaration names complete and aligned.
|
|
45
|
+
* @param {Array<Object>} rows - Report rows
|
|
46
|
+
* @returns {{ name: number, key: number }} Result-specific widths
|
|
47
|
+
*/
|
|
48
|
+
function reportWidths(rows) {
|
|
49
|
+
return {
|
|
50
|
+
name: Math.max(34, ...rows.map(row => String(row.name).length)),
|
|
51
|
+
key: Math.max(44, ...rows.map(row => String(row.catalogKey || '—').length))
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Print the installation before the first write. Pushing into the wrong installation is
|
|
57
|
+
* silent and unrecoverable, because no value can be read back to show what was overwritten.
|
|
58
|
+
* @param {Object} ctx - controllerUrl, envKey
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
function reportTarget(ctx) {
|
|
62
|
+
logger.log(`Controller: ${ctx.controllerUrl} environment: ${ctx.envKey}`);
|
|
63
|
+
logger.log('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {Array<Object>} results - Reported rows
|
|
68
|
+
* @param {boolean} dryRun - Whether anything was written
|
|
69
|
+
* @returns {number} Exit code: non-zero when any row failed
|
|
70
|
+
*/
|
|
71
|
+
function reportSummary(results, dryRun) {
|
|
72
|
+
const failed = results.filter(row => row.outcome !== OUTCOME.updated && row.outcome !== OUTCOME.skipped);
|
|
73
|
+
const updated = results.filter(row => row.outcome === OUTCOME.updated);
|
|
74
|
+
const skipped = results.filter(row => row.outcome === OUTCOME.skipped);
|
|
75
|
+
// Count only the rows that would actually be written. Counting skipped rows as "would be
|
|
76
|
+
// pushed" overstates what the command does, which is the one thing a dry run must not do.
|
|
77
|
+
const considered = results.length - skipped.length;
|
|
78
|
+
logger.log('');
|
|
79
|
+
if (dryRun) {
|
|
80
|
+
const tail = skipped.length > 0 ? ` ${skipped.length} skipped.` : '';
|
|
81
|
+
logger.log(`Dry run: ${updated.length} of ${considered} configuration value(s) would be pushed.${tail} Nothing was written.`);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
if (failed.length === 0) {
|
|
85
|
+
logger.log(`${updated.length} of ${considered} pushed.`);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
logger.log(`${updated.length} of ${considered} pushed; ${failed.length} failed.`);
|
|
89
|
+
reportRemediation(failed);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* One hint per failure class, because each has a different owner.
|
|
95
|
+
* @param {Array<Object>} failed - Failing rows
|
|
96
|
+
* @returns {void}
|
|
97
|
+
*/
|
|
98
|
+
function reportRemediation(failed) {
|
|
99
|
+
if (failed.some(row => row.outcome === OUTCOME.notInCatalog)) {
|
|
100
|
+
logger.log('The controller no longer recognizes those configuration declarations.');
|
|
101
|
+
logger.log('Refresh with aifabrix env secret list, then retry using the current declaration.');
|
|
102
|
+
}
|
|
103
|
+
if (failed.some(row => row.outcome === OUTCOME.forbidden)) {
|
|
104
|
+
logger.log('Setting a system secret requires the controller:admin scope. Run: aifabrix login');
|
|
105
|
+
}
|
|
106
|
+
if (failed.some(row => row.outcome === OUTCOME.unreachable)) {
|
|
107
|
+
logger.log('Nothing was written for those keys. Check the controller URL above and retry.');
|
|
108
|
+
}
|
|
109
|
+
if (failed.some(row => row.outcome === OUTCOME.conflict)) {
|
|
110
|
+
logger.log('The configuration changed before the write. Run the push again to use a fresh revision.');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Map .env variable names to authoritative Controller declarations.
|
|
116
|
+
* @param {string} envContent - Application .env content
|
|
117
|
+
* @param {Array<Object>} declarations - Controller configuration rows
|
|
118
|
+
* @param {string} [only] - Optional exact .env variable name
|
|
119
|
+
* @param {string} [application] - Expected exact application owner
|
|
120
|
+
* @returns {{ pushable: Array<Object>, skipped: Array<Object> }} Revision-bound rows
|
|
121
|
+
*/
|
|
122
|
+
function mapEnvConfiguration(envContent, declarations, only, application) {
|
|
123
|
+
const pushable = [];
|
|
124
|
+
const skipped = [];
|
|
125
|
+
const values = parseEnvAssignments(envContent);
|
|
126
|
+
for (const [name, value] of values) {
|
|
127
|
+
if (only && name !== only) continue;
|
|
128
|
+
const matches = declarations.filter(item => item && item.name === name &&
|
|
129
|
+
(!application || item.application === application));
|
|
130
|
+
if (matches.length !== 1) {
|
|
131
|
+
skipped.push({ name, reason: matches.length ? 'ambiguous declaration' : 'not declared' });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const match = matches[0];
|
|
135
|
+
if (typeof match.sensitive !== 'boolean' || !match.key || !match.application || !match.revision) {
|
|
136
|
+
skipped.push({ name, catalogKey: match.key, reason: 'incomplete declaration metadata' });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (match.sensitive && !value) {
|
|
140
|
+
skipped.push({ name, catalogKey: match.key, reason: 'no secret value in the env file' });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
pushable.push({
|
|
144
|
+
name, value, catalogKey: match.key, application: match.application,
|
|
145
|
+
revision: match.revision, sensitive: match.sensitive
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (only && !values.has(only)) skipped.push({ name: only, reason: 'not present in the env file' });
|
|
149
|
+
return { pushable, skipped };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Object} deps - Injected api
|
|
154
|
+
* @param {Object} ctx - controllerUrl, authConfig, envKey
|
|
155
|
+
* @param {Array<Object>} ready - Rows to push
|
|
156
|
+
* @returns {Promise<Array<Object>>} Reported rows
|
|
157
|
+
*/
|
|
158
|
+
async function pushAll(deps, ctx, ready) {
|
|
159
|
+
const results = [];
|
|
160
|
+
for (const row of ready) {
|
|
161
|
+
const result = await pushRow(deps, ctx, row);
|
|
162
|
+
results.push(result);
|
|
163
|
+
if (result.fatal) {
|
|
164
|
+
// 403 fails identically for every remaining key; report them rather than retry.
|
|
165
|
+
for (const remaining of ready.slice(results.length)) {
|
|
166
|
+
results.push({ name: remaining.name, catalogKey: remaining.catalogKey, outcome: result.outcome });
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Run the push.
|
|
176
|
+
* @param {Object} ctx - controllerUrl, authConfig, envKey, envFilePath
|
|
177
|
+
* @param {Object} [options] - `key`, `dryRun`, `api`
|
|
178
|
+
* @returns {Promise<{ exitCode: number, results: Array<Object> }>} Outcome per row
|
|
179
|
+
*/
|
|
180
|
+
async function runEnvSecretPush(ctx, options = {}) {
|
|
181
|
+
const envContent = readEnvFile(ctx.envFilePath, options);
|
|
182
|
+
const api = options.api || {
|
|
183
|
+
listEnvironmentConfiguration, putSystemSecret, putEnvironmentConfiguration
|
|
184
|
+
};
|
|
185
|
+
const declarations = await api.listEnvironmentConfiguration(
|
|
186
|
+
ctx.controllerUrl, ctx.authConfig, ctx.envKey, options.application
|
|
187
|
+
);
|
|
188
|
+
const { pushable, skipped } = mapEnvConfiguration(
|
|
189
|
+
envContent, declarations, options.key, options.application
|
|
190
|
+
);
|
|
191
|
+
reportTarget(ctx);
|
|
192
|
+
// Every mapped row is attempted. Whether an unlisted key can be written differs by
|
|
193
|
+
// installation, so only the server can answer it.
|
|
194
|
+
const results = options.dryRun
|
|
195
|
+
? dryRunRows(pushable)
|
|
196
|
+
: await pushAll(options, ctx, pushable);
|
|
197
|
+
const reported = results.concat(skipped.map(row => ({ ...row, outcome: OUTCOME.skipped })));
|
|
198
|
+
const widths = reportWidths(reported);
|
|
199
|
+
for (const row of reported) {
|
|
200
|
+
logger.log(formatRow(row, widths));
|
|
201
|
+
}
|
|
202
|
+
return { exitCode: reportSummary(reported, options.dryRun === true), results: reported };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Dry-run rows: what would be attempted. Catalog validity is decided by PUT on a real run.
|
|
207
|
+
* @param {Array<Object>} pushable - Mapped rows
|
|
208
|
+
* @returns {Array<Object>} Reportable rows that write nothing
|
|
209
|
+
*/
|
|
210
|
+
function dryRunRows(pushable) {
|
|
211
|
+
return pushable.map(row => ({
|
|
212
|
+
name: row.name,
|
|
213
|
+
catalogKey: row.catalogKey,
|
|
214
|
+
outcome: OUTCOME.updated
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
formatRow,
|
|
220
|
+
reportWidths,
|
|
221
|
+
reportTarget,
|
|
222
|
+
reportSummary,
|
|
223
|
+
dryRunRows,
|
|
224
|
+
pushAll,
|
|
225
|
+
mapEnvConfiguration,
|
|
226
|
+
runEnvSecretPush
|
|
227
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push declared configuration values from an operator's env file into an installation.
|
|
3
|
+
*
|
|
4
|
+
* Every value in this module comes from a file and goes to one API call. It is never an
|
|
5
|
+
* argument (so it cannot reach shell history or a process list), never logged, and never
|
|
6
|
+
* placed in a message, a progress line or a JSON envelope — on success or on failure.
|
|
7
|
+
* Rows are reported by variable name, declaration key and outcome only.
|
|
8
|
+
*
|
|
9
|
+
* The command updates Controller declarations and cannot create a new declaration.
|
|
10
|
+
*
|
|
11
|
+
* @fileoverview aifabrix env secret push
|
|
12
|
+
* @author AI Fabrix Team
|
|
13
|
+
* @version 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { execFileSync } = require('child_process');
|
|
21
|
+
const { putSystemSecret } = require('../api/system-secrets.api');
|
|
22
|
+
const {
|
|
23
|
+
listEnvironmentConfiguration,
|
|
24
|
+
putEnvironmentConfiguration
|
|
25
|
+
} = require('../api/configuration.api');
|
|
26
|
+
|
|
27
|
+
/** Outcome codes reported per row; none of them can carry a value. */
|
|
28
|
+
const OUTCOME = Object.freeze({
|
|
29
|
+
updated: 'updated',
|
|
30
|
+
notInCatalog: 'not declared',
|
|
31
|
+
forbidden: 'not permitted',
|
|
32
|
+
conflict: 'configuration changed; retry',
|
|
33
|
+
unreachable: 'controller unreachable',
|
|
34
|
+
skipped: 'skipped'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* HTTP status from an API client error, when it carries one.
|
|
39
|
+
* @param {Error} error - Thrown error
|
|
40
|
+
* @returns {number} Status code, or 0 when the failure was not an HTTP response
|
|
41
|
+
*/
|
|
42
|
+
function statusOf(error) {
|
|
43
|
+
const status = error && (error.status || error.statusCode ||
|
|
44
|
+
(error.response && error.response.status));
|
|
45
|
+
return Number.isInteger(status) ? status : 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The API client resolves non-2xx responses as `{ success: false, status }` rather than
|
|
50
|
+
* throwing, so a failure that is only caught in a try/catch is reported as success. Verified
|
|
51
|
+
* against the live controller: a PUT to an undefined catalog key resolves with status 404.
|
|
52
|
+
* @param {Object} response - Resolved API response
|
|
53
|
+
* @returns {boolean} True when the call did not succeed
|
|
54
|
+
*/
|
|
55
|
+
function isFailedResponse(response) {
|
|
56
|
+
if (!response || typeof response !== 'object') {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (response.success === false) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const status = statusOf(response);
|
|
63
|
+
return status >= 400;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Classify a failure by owner. 403, 404 and transport failures need different messages
|
|
68
|
+
* because different people fix them; collapsing them to "failed to push" helps nobody.
|
|
69
|
+
* @param {Error} error - Thrown error
|
|
70
|
+
* @returns {{ outcome: string, fatal: boolean }} Outcome and whether remaining keys are pointless
|
|
71
|
+
*/
|
|
72
|
+
function classifyFailure(error) {
|
|
73
|
+
const status = statusOf(error);
|
|
74
|
+
if (status === 403 || status === 401) {
|
|
75
|
+
// Every remaining key fails the same way, so stop rather than repeat it.
|
|
76
|
+
return { outcome: OUTCOME.forbidden, fatal: true };
|
|
77
|
+
}
|
|
78
|
+
if (status === 404) {
|
|
79
|
+
return { outcome: OUTCOME.notInCatalog, fatal: false };
|
|
80
|
+
}
|
|
81
|
+
if (status === 409) {
|
|
82
|
+
return { outcome: OUTCOME.conflict, fatal: true };
|
|
83
|
+
}
|
|
84
|
+
return { outcome: OUTCOME.unreachable, fatal: false };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A secrets file that git does not ignore is one `git add .` away from being committed, and
|
|
89
|
+
* a committed secret is disclosed no matter what happens afterwards. Refuse rather than
|
|
90
|
+
* read it. An untracked path outside any repository is fine — there is nothing to commit to.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} filePath - Env file path
|
|
93
|
+
* @param {Object} [deps] - `execFileSync` for tests
|
|
94
|
+
* @returns {boolean} True when git ignores the file, or the path is not in a repository
|
|
95
|
+
*/
|
|
96
|
+
function isIgnoredByGit(filePath, deps = {}) {
|
|
97
|
+
const exec = deps.execFileSync || execFileSync;
|
|
98
|
+
const cwd = path.dirname(filePath);
|
|
99
|
+
try {
|
|
100
|
+
exec('git', ['check-ignore', '-q', '--', filePath], { cwd, stdio: 'ignore' });
|
|
101
|
+
return true;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// check-ignore exits 1 when the path is NOT ignored, and 128 outside a repository.
|
|
104
|
+
return statusOf(error) === 128 || (error && error.status === 128);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} filePath - Env file path
|
|
110
|
+
* @param {Object} [deps] - `execFileSync` for tests
|
|
111
|
+
* @returns {string} File content
|
|
112
|
+
* @throws {Error} When the file is missing or git does not ignore it
|
|
113
|
+
*/
|
|
114
|
+
function readEnvFile(filePath, deps = {}) {
|
|
115
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
116
|
+
throw new Error(`Env file not found: ${filePath || '(no path resolved)'}`);
|
|
117
|
+
}
|
|
118
|
+
if (!isIgnoredByGit(filePath, deps)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Refusing to read ${filePath}: git does not ignore it. ` +
|
|
121
|
+
'A secrets file inside a tracked path can be committed by accident. ' +
|
|
122
|
+
'Add it to .gitignore, or pass --file with a path outside the repository.'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Push one row. Returns an outcome; never returns or throws the value.
|
|
130
|
+
* @param {Object} deps - Injected api
|
|
131
|
+
* @param {Object} ctx - controllerUrl, authConfig, envKey
|
|
132
|
+
* @param {Object} row - name, catalogKey, value
|
|
133
|
+
* @returns {Promise<{ name: string, catalogKey: string, outcome: string, fatal?: boolean }>} Result
|
|
134
|
+
*/
|
|
135
|
+
async function pushRow(deps, ctx, row) {
|
|
136
|
+
const api = deps.api || {
|
|
137
|
+
putSystemSecret, listEnvironmentConfiguration, putEnvironmentConfiguration
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
const common = [ctx.controllerUrl, ctx.authConfig, ctx.envKey];
|
|
141
|
+
const response = row.sensitive
|
|
142
|
+
? await api.putSystemSecret(
|
|
143
|
+
...common, row.catalogKey, row.value, row.revision
|
|
144
|
+
)
|
|
145
|
+
: await api.putEnvironmentConfiguration(
|
|
146
|
+
...common, row.application, row.catalogKey,
|
|
147
|
+
{ value: row.value, revision: row.revision }
|
|
148
|
+
);
|
|
149
|
+
if (isFailedResponse(response)) {
|
|
150
|
+
const { outcome, fatal } = classifyFailure(response);
|
|
151
|
+
return { name: row.name, catalogKey: row.catalogKey, outcome, fatal };
|
|
152
|
+
}
|
|
153
|
+
return { name: row.name, catalogKey: row.catalogKey, outcome: OUTCOME.updated };
|
|
154
|
+
} catch (error) {
|
|
155
|
+
const { outcome, fatal } = classifyFailure(error);
|
|
156
|
+
return { name: row.name, catalogKey: row.catalogKey, outcome, fatal };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
OUTCOME,
|
|
162
|
+
classifyFailure,
|
|
163
|
+
statusOf,
|
|
164
|
+
isIgnoredByGit,
|
|
165
|
+
isFailedResponse,
|
|
166
|
+
readEnvFile,
|
|
167
|
+
pushRow
|
|
168
|
+
};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
const { repairKeyedDocument } = require('./repair-datasource-keyed-document');
|
|
11
12
|
const { repairOpenapiSection } = require('./repair-datasource-openapi');
|
|
12
13
|
const { repairSyncOperationsFromCapabilities } = require('./repair-datasource-sync-operations');
|
|
13
14
|
const { normalizeAutoRbacOperationKeys } = require('./repair-datasource-auto-rbac');
|
|
@@ -132,6 +133,7 @@ function runRepairDatasourceFile(parsed, options, changes, helpers) {
|
|
|
132
133
|
|
|
133
134
|
const none = helpers.isNoneEntityType(parsed?.entityType);
|
|
134
135
|
|
|
136
|
+
updated = repairKeyedDocument(parsed, options, out) || updated;
|
|
135
137
|
updated = applyBaseDatasourceRepairs(parsed, none, out, options, helpers) || updated;
|
|
136
138
|
updated = applyOpenapiDatasourceRepairs(parsed, out) || updated;
|
|
137
139
|
updated = applyOptionalDatasourceRepairs(parsed, options, none, out, helpers) || updated;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Generate keyed document retrieval from vendor OpenAPI response media types.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const { toCamelCaseKey } = require('./repair-datasource-auto-rbac');
|
|
9
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
10
|
+
const { documentKeyToLocalOpenApiPath } = require('./repair-openapi-export');
|
|
11
|
+
const { findVendorOperation } = require('../validation/openapi-contract-vendor-validation');
|
|
12
|
+
|
|
13
|
+
/** @param {Object} ds @param {Object} options @returns {Object|null} */
|
|
14
|
+
function loadVendorSpec(ds, options) {
|
|
15
|
+
if (options.openapiSpec) return options.openapiSpec;
|
|
16
|
+
if (!options.appPath || !ds.openapi?.documentKey) return null;
|
|
17
|
+
const file = documentKeyToLocalOpenApiPath(options.appPath, ds.systemKey || '', ds.openapi.documentKey);
|
|
18
|
+
return fs.existsSync(file) ? loadConfigFile(file) : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @param {Object} operation @returns {Object[]} */
|
|
22
|
+
function binaryResponses(operation) {
|
|
23
|
+
return Object.entries(operation?.responses || {})
|
|
24
|
+
.filter(([status]) => /^2(?:\d\d|XX)$/i.test(status))
|
|
25
|
+
.map(([, response]) => response)
|
|
26
|
+
.filter(response => Object.keys(response?.content || {}).some(type =>
|
|
27
|
+
/^(application\/(octet-stream|pdf)|image\/[^;]+)(;|$)/i.test(type)));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {Object} ds @param {Object} operation @param {Object[]} responses */
|
|
31
|
+
function addMetadataMappings(ds, operation, responses) {
|
|
32
|
+
ds.fieldMappings = ds.fieldMappings || {};
|
|
33
|
+
ds.fieldMappings.attributes = ds.fieldMappings.attributes || {};
|
|
34
|
+
const attributes = ds.fieldMappings.attributes;
|
|
35
|
+
for (const response of responses) {
|
|
36
|
+
for (const name of Object.keys(response.headers || {})) {
|
|
37
|
+
addMappingStub(attributes, name, 'headers');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const param of operation.parameters || []) {
|
|
41
|
+
if (param.required && ['query', 'path'].includes(param.in) && param.name) {
|
|
42
|
+
addMappingStub(attributes, param.name, 'params');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @param {Object} attributes @param {string} name @param {string} source */
|
|
48
|
+
function addMappingStub(attributes, name, source) {
|
|
49
|
+
const key = toCamelCaseKey(name);
|
|
50
|
+
if (!/^[a-z][a-zA-Z0-9]*$/.test(key) || Object.hasOwn(attributes, key)) return;
|
|
51
|
+
attributes[key] = { expression: `{{raw.${source}.${name}}}` };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** @param {Object} ds @param {string} ref @returns {Object[]} */
|
|
55
|
+
function ensureGetSteps(ds, ref) {
|
|
56
|
+
ds.execution = ds.execution || {};
|
|
57
|
+
ds.execution.engine = 'cip';
|
|
58
|
+
ds.execution.cip = ds.execution.cip || { version: '1.0', operations: {} };
|
|
59
|
+
ds.execution.cip.operations = ds.execution.cip.operations || {};
|
|
60
|
+
const operations = ds.execution.cip.operations;
|
|
61
|
+
if (!operations.get) {
|
|
62
|
+
operations.get = { steps: [
|
|
63
|
+
{ fetch: { source: 'openapi', openapiRef: ref, responseType: 'binary' } },
|
|
64
|
+
{ map: { useFieldMappings: true } },
|
|
65
|
+
{ filter: { abac: { mode: 'mandatory' } } },
|
|
66
|
+
{ output: { mode: 'documents' } }
|
|
67
|
+
] };
|
|
68
|
+
}
|
|
69
|
+
return Array.isArray(operations.get.steps) ? operations.get.steps : [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Leave list-based document sync and author-owned unrelated operations unchanged.
|
|
74
|
+
* @param {Object} ds - Datasource, mutated
|
|
75
|
+
* @param {Object} options - Vendor spec or local appPath
|
|
76
|
+
* @param {string[]} changes - Repair log
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
function repairKeyedDocument(ds, options = {}, changes = []) {
|
|
80
|
+
const candidate = keyedDocumentCandidate(ds, options);
|
|
81
|
+
if (!candidate) return false;
|
|
82
|
+
const { ref, vendor, responses } = candidate;
|
|
83
|
+
const before = JSON.stringify(ds);
|
|
84
|
+
const getSteps = ensureGetSteps(ds, ref);
|
|
85
|
+
if (!getSteps.some(step => step?.fetch) || !getSteps.some(step => step?.output)) return false;
|
|
86
|
+
for (const step of getSteps) {
|
|
87
|
+
if (step?.fetch && (step.fetch.openapiRef || 'get') === ref) step.fetch.responseType = 'binary';
|
|
88
|
+
if (step?.output) step.output.mode = 'documents';
|
|
89
|
+
}
|
|
90
|
+
ds.documentStorage = { enabled: true, ingestAfterSync: true,
|
|
91
|
+
...ds.documentStorage, responseType: 'binary', binaryOperationRef: 'get' };
|
|
92
|
+
addMetadataMappings(ds, vendor, responses);
|
|
93
|
+
if (JSON.stringify(ds) === before) return false;
|
|
94
|
+
changes.push('Configured keyed binary get with documents output and header/parameter mappings');
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @param {Object} ds @param {Object} options @returns {Object|null} */
|
|
99
|
+
function keyedDocumentCandidate(ds, options) {
|
|
100
|
+
if (!['documentStorage', 'document-storage'].includes(ds?.entityType)) return null;
|
|
101
|
+
if (!canGenerateKeyedDocument(ds)) return null;
|
|
102
|
+
const operations = ds.execution?.cip?.operations || {};
|
|
103
|
+
const steps = operations.get?.steps || [];
|
|
104
|
+
const fetch = steps.find(step => step?.fetch)?.fetch;
|
|
105
|
+
if (fetch && !['openapi', undefined].includes(fetch.source)) return null;
|
|
106
|
+
const ref = fetch?.openapiRef || 'get';
|
|
107
|
+
const binding = ds.openapi?.operations?.[ref];
|
|
108
|
+
const vendor = findVendorOperation(loadVendorSpec(ds, options), binding);
|
|
109
|
+
const responses = binaryResponses(vendor);
|
|
110
|
+
return responses.length ? { ref, vendor, responses } : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @param {Object} ds @returns {boolean} */
|
|
114
|
+
function canGenerateKeyedDocument(ds) {
|
|
115
|
+
if (ds.execution?.cip?.operations?.list || ds.openapi?.operations?.list) return false;
|
|
116
|
+
if (ds.capabilities?.some(cap => (cap.key || cap) === 'list')) return false;
|
|
117
|
+
if (ds.documentStorage?.enabled === false) return false;
|
|
118
|
+
if (ds.execution?.engine && ds.execution.engine !== 'cip') return false;
|
|
119
|
+
return !ds.documentStorage?.binaryOperationRef || ds.documentStorage.binaryOperationRef === 'get';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { repairKeyedDocument };
|
|
@@ -37,6 +37,7 @@ function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes
|
|
|
37
37
|
try {
|
|
38
38
|
const parsed = loadConfigFile(filePath);
|
|
39
39
|
const { updated: fileUpdated, changes: fileChanges } = repairDatasourceFile(parsed, {
|
|
40
|
+
appPath,
|
|
40
41
|
expose: options.expose,
|
|
41
42
|
sync: options.sync,
|
|
42
43
|
test: options.test,
|
|
@@ -41,6 +41,10 @@ const {
|
|
|
41
41
|
patchCaseStatusAfterRun
|
|
42
42
|
} = require('../role-assistant/test-pack-sync');
|
|
43
43
|
const { collectString } = require('./governance');
|
|
44
|
+
const {
|
|
45
|
+
hasRoleAssistantTestSubject,
|
|
46
|
+
runPublishedCaseAsSubject
|
|
47
|
+
} = require('../role-assistant/test-job-runner');
|
|
44
48
|
const {
|
|
45
49
|
sectionTitle,
|
|
46
50
|
headerKeyValue,
|
|
@@ -338,6 +342,9 @@ async function resolveExecutableCaseResult(roleKey, roleCase, opts, result, verd
|
|
|
338
342
|
run: false,
|
|
339
343
|
noPublish: true
|
|
340
344
|
});
|
|
345
|
+
if (hasRoleAssistantTestSubject(opts)) {
|
|
346
|
+
return runPublishedCaseAsSubject(roleKey, roleCase, opts, authContext);
|
|
347
|
+
}
|
|
341
348
|
const evidencePreflight = await assertRoleAssistantEvidencePreflight(roleCase, authContext);
|
|
342
349
|
const executionResult = await runRoleAssistantCaseLifecycle(roleKey, roleCase, authContext, opts);
|
|
343
350
|
const executionVerdict = lifecycleVerdictFromCaseExecution(executionResult, verdict);
|
|
@@ -322,8 +322,7 @@ function removeUserLocalSecrets() {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
/**
|
|
325
|
-
* Fresh install: wizard collects admin
|
|
326
|
-
* then up-infra + up-platform.
|
|
325
|
+
* Fresh install: wizard collects admin credentials, then runs up-infra + up-platform.
|
|
327
326
|
*
|
|
328
327
|
* @async
|
|
329
328
|
* @param {{ adminEmail: string, adminPassword?: string, passwordBundle?: object }} adminCreds
|
|
@@ -345,9 +344,6 @@ async function runFreshInstall(adminCreds) {
|
|
|
345
344
|
});
|
|
346
345
|
stopSpinnerSuccess(infraSpin, 'Infrastructure ready');
|
|
347
346
|
await runSetupImageRefresh({ quiet: true });
|
|
348
|
-
// Bring up infra first, then collect optional platform-level secrets (AI tool)
|
|
349
|
-
// so the flow feels like a product installer: infra → platform configuration → platform services.
|
|
350
|
-
await setupPrompts.promptAiTool({ silentIfConfigured: true });
|
|
351
347
|
// Fresh-install should reset dev defaults (env + controller URL) the same way
|
|
352
348
|
// as other setup recovery paths, otherwise the platform can come up pointed at
|
|
353
349
|
// stale configuration.
|
|
@@ -382,7 +378,6 @@ async function runReinstall() {
|
|
|
382
378
|
await runSetupImageRefresh({ quiet: true });
|
|
383
379
|
const adminCreds = await setupPrompts.promptAdminCredentials();
|
|
384
380
|
await config.setAdminEmail(adminCreds.adminEmail);
|
|
385
|
-
await setupPrompts.promptAiTool({ silentIfConfigured: true });
|
|
386
381
|
const infraSpin = startSpinner('Starting infrastructure...');
|
|
387
382
|
await startInfraFromConfig({
|
|
388
383
|
adminEmail: adminCreds.adminEmail,
|
|
@@ -436,7 +431,6 @@ async function runWipeData() {
|
|
|
436
431
|
});
|
|
437
432
|
stopSpinnerSuccess(wipeSpin, 'Databases reset');
|
|
438
433
|
await runSetupImageRefresh({ quiet: true });
|
|
439
|
-
await setupPrompts.promptAiTool({ silentIfConfigured: true });
|
|
440
434
|
const infraSpin = startSpinner('Starting infrastructure...');
|
|
441
435
|
await startInfraFromConfig({});
|
|
442
436
|
stopSpinnerSuccess(infraSpin, 'Infrastructure ready');
|
|
@@ -460,7 +454,6 @@ async function runWipeData() {
|
|
|
460
454
|
*/
|
|
461
455
|
async function runUpdateImages() {
|
|
462
456
|
await runSetupImageRefresh({ quiet: true });
|
|
463
|
-
await setupPrompts.promptAiTool({ silentIfConfigured: true });
|
|
464
457
|
const infraSpin = startSpinner('Starting infrastructure...');
|
|
465
458
|
await startInfraFromConfig({});
|
|
466
459
|
stopSpinnerSuccess(infraSpin, 'Infrastructure ready');
|