@aifabrix/builder 2.60.0 → 2.62.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 +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/api/applications.api.js +22 -2
- package/lib/api/bootstrap-snapshot.api.js +115 -0
- package/lib/api/configuration.api.js +131 -0
- package/lib/api/system-secrets.api.js +72 -0
- package/lib/app/deploy.js +14 -5
- package/lib/app/run-docker-fallback.js +6 -1
- package/lib/app/run-env-compose.js +5 -6
- package/lib/app/run-helpers.js +2 -1
- package/lib/app/run-parameter-sync.js +142 -0
- package/lib/app/run.js +6 -4
- package/lib/build/docker-build-args.js +5 -2
- package/lib/build/index.js +3 -2
- package/lib/build/standard-docker-build.js +6 -2
- package/lib/channels/channel-artifact-generator.js +13 -3
- package/lib/cli/infra-guided-footers.js +10 -8
- package/lib/cli/infra-guided.js +30 -10
- package/lib/cli/setup-app.js +5 -0
- package/lib/cli/setup-environment.js +156 -0
- package/lib/cli/setup-infra-up-platform-action.js +14 -1
- package/lib/cli/setup-platform.js +2 -0
- package/lib/commands/auth-config.js +2 -0
- 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/login.js +4 -0
- package/lib/commands/repair-datasource-apply.js +5 -1
- package/lib/commands/repair-datasource-keyed-document.js +122 -0
- package/lib/commands/repair-datasource-legacy.js +94 -0
- package/lib/commands/repair-datasource-run.js +1 -0
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/setup-modes.js +8 -9
- package/lib/commands/setup-prompts-platform-mode.js +34 -0
- package/lib/commands/setup-prompts.js +2 -180
- package/lib/commands/setup.js +3 -0
- package/lib/commands/up-builder-api.js +24 -8
- package/lib/commands/up-common.js +16 -1
- package/lib/commands/up-dataplane-bootstrap.js +70 -14
- package/lib/commands/up-dataplane-credentials.js +21 -0
- package/lib/commands/up-dataplane.js +37 -18
- package/lib/commands/up-integration-server.js +112 -0
- 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 +12 -5
- package/lib/commands/wizard-core.js +5 -157
- package/lib/commands/wizard-file-saving.js +163 -0
- package/lib/core/admin-secrets-env-overlay.js +8 -3
- package/lib/core/env-platform-expand.js +5 -1
- package/lib/core/environment-access-policy.js +121 -0
- package/lib/core/environment-mode-policy.js +177 -0
- package/lib/core/local-env-overrides.js +149 -0
- package/lib/core/secrets-ensure.js +4 -2
- package/lib/core/secrets-env-content.js +17 -10
- package/lib/core/secrets-env-write.js +11 -4
- package/lib/core/secrets-load.js +4 -2
- package/lib/datasource/binary-documents-validator.js +190 -0
- package/lib/datasource/capability/capability-manifest.js +1 -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/index.js +14 -1
- package/lib/deployment/installation/infra-catalog.js +3 -0
- package/lib/deployment/installation/local-environment-stage.js +285 -0
- package/lib/deployment/installation/local-installation.js +76 -0
- package/lib/deployment/installation/stage-input.js +5 -1
- package/lib/generator/builders.js +3 -1
- package/lib/generator/index.js +3 -0
- package/lib/generator/split-variables.js +2 -2
- package/lib/lifecycle/product-model.js +4 -3
- package/lib/lifecycle/report-display.js +3 -2
- package/lib/parameters/database-adoption.js +125 -0
- package/lib/parameters/infra-parameter-catalog.js +1 -1
- package/lib/parameters/physical-database-naming.js +136 -0
- package/lib/parameters/physical-secret-name.js +194 -0
- package/lib/parameters/rsa-secret-values.js +165 -0
- 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/programmatic/run-channel-install-artifacts.js +28 -2
- package/lib/programmatic/run-channel-package.js +3 -1
- package/lib/programmatic/workspace-context.js +37 -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 +14 -2
- package/lib/schema/external-datasource.schema.json +23 -3
- package/lib/schema/infra.parameter.yaml +440 -56
- package/lib/schema/infrastructure-schema.json +213 -120
- package/lib/utils/compose-generate-docker-compose.js +9 -14
- package/lib/utils/compose-generator.js +4 -7
- package/lib/utils/compose-miso-env.js +2 -2
- package/lib/utils/compose-traefik-ingress-base.js +3 -4
- 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/declarative-url-ports.js +3 -0
- package/lib/utils/env-copy.js +11 -10
- package/lib/utils/env-environment-file-paths.js +105 -0
- package/lib/utils/environment-scoped-resources.js +48 -14
- package/lib/utils/external-system-system-test-tty.js +3 -2
- package/lib/utils/image-tags.js +2 -2
- package/lib/utils/paths-system-builder-keys.js +7 -1
- package/lib/utils/paths.js +7 -2
- package/lib/utils/platform-kv-ref.js +1 -1
- package/lib/utils/postgres-platform-bootstrap.js +35 -1
- package/lib/utils/prepare-local-data-mount.js +58 -0
- package/lib/utils/redis-env-scope.js +21 -1
- package/lib/utils/registry-auth-sources.js +154 -0
- package/lib/utils/registry-credentials.js +131 -14
- package/lib/utils/secrets-generator.js +30 -21
- package/lib/utils/secrets-helpers.js +5 -8
- package/lib/utils/secrets-kv-scope.js +127 -28
- package/lib/utils/secrets-materialize-local.js +9 -0
- package/lib/utils/secrets-missing-error.js +35 -1
- package/lib/utils/system-secret-mapping.js +125 -0
- package/lib/utils/test-log-writer.js +2 -1
- package/lib/utils/token-manager.js +14 -8
- package/lib/utils/url-public-path-prefix.js +6 -10
- 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 +6 -4
- package/templates/agent-kit/agent-kit.yaml +1 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -2
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +6 -2
- package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +7 -1
- package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
- package/templates/agent-kit/skills/shared/interaction.md +83 -0
- package/templates/agent-kit/skills/shared/status.md +3 -12
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/builder-api/env.template +5 -1
- package/templates/applications/dataplane/application.yaml +2 -2
- package/templates/applications/dataplane/env.template +10 -12
- package/templates/applications/integration-server/application.yaml +91 -0
- package/templates/applications/integration-server/env.template +215 -0
- package/templates/applications/keycloak/application.yaml +6 -1
- package/templates/applications/mirrored-templates.json +52 -0
- package/templates/applications/miso-controller/application.yaml +116 -2
- package/templates/applications/miso-controller/env.template +62 -17
- package/templates/applications/miso-controller/rbac.yaml +17 -0
- package/templates/external-system/external-datasource.yaml.hbs +7 -1
- package/templates/marketplace/createUiDefinition.json +52 -4
- package/templates/marketplace/main.json +88 -10
- package/templates/python/docker-compose.hbs +1 -1
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -41
- /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,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show compact configuration status without exposing sensitive values.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview aifabrix env secret list
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { listEnvironmentConfiguration } = require('../api/configuration.api');
|
|
13
|
+
const {
|
|
14
|
+
colorAggregateGlyph,
|
|
15
|
+
metadata,
|
|
16
|
+
sectionTitle
|
|
17
|
+
} = require('../utils/cli-layout-chalk');
|
|
18
|
+
|
|
19
|
+
const FORBIDDEN_FIELDS = ['data', 'ciphertext', 'reference', 'vaultName', 'secretValue'];
|
|
20
|
+
const WIDTHS = Object.freeze({ status: 6, application: 20, name: 28, type: 8, value: 48 });
|
|
21
|
+
const GLYPHS = Object.freeze({ ok: '✔', warn: '⚠', skipped: '⏭' });
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Object} row - Configuration metadata row
|
|
25
|
+
* @param {string} application - Optional expected owner
|
|
26
|
+
* @returns {void} Throws when unsafe or contradictory
|
|
27
|
+
*/
|
|
28
|
+
function assertSafeRow(row, application) {
|
|
29
|
+
if (!row || typeof row !== 'object') {
|
|
30
|
+
throw new Error('Configuration list returned invalid or unsafe metadata');
|
|
31
|
+
}
|
|
32
|
+
const owner = String(row.application || '').trim();
|
|
33
|
+
const name = String(row.name || row.key || '').trim();
|
|
34
|
+
const hasValue = Object.prototype.hasOwnProperty.call(row, 'value');
|
|
35
|
+
const safeValue = [!hasValue, row.sensitive === false && typeof row.value === 'string']
|
|
36
|
+
.some(Boolean);
|
|
37
|
+
const forbidden = FORBIDDEN_FIELDS.some(field =>
|
|
38
|
+
Object.prototype.hasOwnProperty.call(row, field)
|
|
39
|
+
);
|
|
40
|
+
const exactOwner = [!application, owner === application].some(Boolean);
|
|
41
|
+
const booleans = ['sensitive', 'configured', 'required']
|
|
42
|
+
.every(field => typeof row[field] === 'boolean');
|
|
43
|
+
const valid = [owner, name, exactOwner, booleans, safeValue, !forbidden].every(Boolean);
|
|
44
|
+
if (!valid) {
|
|
45
|
+
throw new Error('Configuration list returned invalid or unsafe metadata');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {Object} row - Configuration metadata row
|
|
51
|
+
* @param {string} application - Optional expected exact owner
|
|
52
|
+
* @returns {Object} Safe normalized row
|
|
53
|
+
*/
|
|
54
|
+
function normalizeRow(row, application) {
|
|
55
|
+
assertSafeRow(row, application);
|
|
56
|
+
const owner = String(row.application).trim();
|
|
57
|
+
const name = String(row.name || row.key).trim();
|
|
58
|
+
const hasValue = Object.prototype.hasOwnProperty.call(row, 'value');
|
|
59
|
+
const state = row.configured ? 'ok' : (row.required ? 'warn' : 'skipped');
|
|
60
|
+
const normalized = {
|
|
61
|
+
application: owner,
|
|
62
|
+
name,
|
|
63
|
+
type: row.sensitive ? 'secret' : 'variable',
|
|
64
|
+
configured: row.configured,
|
|
65
|
+
required: row.required,
|
|
66
|
+
state
|
|
67
|
+
};
|
|
68
|
+
if (!row.sensitive && hasValue) normalized.value = row.value;
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {unknown} value - Cell value
|
|
74
|
+
* @returns {string} Safe single-line cell value
|
|
75
|
+
*/
|
|
76
|
+
function cleanCell(value) {
|
|
77
|
+
const noControls = [...String(value ?? '')]
|
|
78
|
+
.map(char => char.charCodeAt(0) <= 31 || char.charCodeAt(0) === 127 ? ' ' : char)
|
|
79
|
+
.join('');
|
|
80
|
+
return noControls.replace(/\s+/g, ' ').trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {unknown} value - Cell value
|
|
85
|
+
* @param {number} width - Visible cell width
|
|
86
|
+
* @returns {string} Single-line padded or truncated cell
|
|
87
|
+
*/
|
|
88
|
+
function formatCell(value, width) {
|
|
89
|
+
const clean = cleanCell(value);
|
|
90
|
+
const visible = clean.length > width ? `${clean.slice(0, width - 1)}…` : clean;
|
|
91
|
+
return visible.padEnd(width);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Keep identifiers complete while retaining compact minimum widths.
|
|
96
|
+
* @param {Array<Object>} rows - Safe normalized rows
|
|
97
|
+
* @returns {Object} Widths for this result set
|
|
98
|
+
*/
|
|
99
|
+
function tableWidths(rows) {
|
|
100
|
+
return {
|
|
101
|
+
...WIDTHS,
|
|
102
|
+
application: Math.max(WIDTHS.application, ...rows.map(row => cleanCell(row.application).length)),
|
|
103
|
+
name: Math.max(WIDTHS.name, ...rows.map(row => cleanCell(row.name).length))
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {Object} row - Safe normalized row
|
|
109
|
+
* @param {boolean} includeApplication - Include owner column
|
|
110
|
+
* @returns {string} One compact table row
|
|
111
|
+
*/
|
|
112
|
+
function formatRow(row, includeApplication, widths = WIDTHS) {
|
|
113
|
+
const glyph = colorAggregateGlyph(row.state, GLYPHS[row.state]);
|
|
114
|
+
const value = row.type === 'variable'
|
|
115
|
+
? (row.value === '' ? '""' : (row.value ?? ''))
|
|
116
|
+
: '';
|
|
117
|
+
const cells = [glyph + ' '.repeat(widths.status - 1)];
|
|
118
|
+
if (includeApplication) cells.push(formatCell(row.application, widths.application));
|
|
119
|
+
cells.push(formatCell(row.name, widths.name));
|
|
120
|
+
cells.push(formatCell(row.type, widths.type));
|
|
121
|
+
cells.push(formatCell(value, widths.value).trimEnd());
|
|
122
|
+
return ` ${cells.join(' ')}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {boolean} includeApplication - Include owner column
|
|
127
|
+
* @returns {string[]} Header and divider
|
|
128
|
+
*/
|
|
129
|
+
function tableHeader(includeApplication, widths = WIDTHS) {
|
|
130
|
+
const headers = [formatCell('Status', widths.status)];
|
|
131
|
+
const dividers = ['-'.repeat(widths.status)];
|
|
132
|
+
if (includeApplication) {
|
|
133
|
+
headers.push(formatCell('Application', widths.application));
|
|
134
|
+
dividers.push('-'.repeat(widths.application));
|
|
135
|
+
}
|
|
136
|
+
headers.push(formatCell('Name', widths.name), formatCell('Type', widths.type), 'Value');
|
|
137
|
+
dividers.push('-'.repeat(widths.name), '-'.repeat(widths.type), '-'.repeat(12));
|
|
138
|
+
return [` ${headers.join(' ')}`, ` ${dividers.join(' ')}`];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {Array<Object>} rows - Safe rows
|
|
143
|
+
* @returns {string} Compact totals
|
|
144
|
+
*/
|
|
145
|
+
function formatSummary(rows) {
|
|
146
|
+
const configured = rows.filter(row => row.configured).length;
|
|
147
|
+
const requiredMissing = rows.filter(row => !row.configured && row.required).length;
|
|
148
|
+
const optionalUnset = rows.length - configured - requiredMissing;
|
|
149
|
+
const ok = colorAggregateGlyph('ok', GLYPHS.ok);
|
|
150
|
+
const warn = colorAggregateGlyph('warn', GLYPHS.warn);
|
|
151
|
+
const skipped = colorAggregateGlyph('skipped', GLYPHS.skipped);
|
|
152
|
+
const label = rows.length === 1 ? 'entry' : 'entries';
|
|
153
|
+
return `${rows.length} ${label} · ${ok} ${configured} configured · ` +
|
|
154
|
+
`${warn} ${requiredMissing} required missing · ${skipped} ${optionalUnset} optional unset`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* List Parameter declarations for an environment or exact application owner.
|
|
159
|
+
* @param {Object} ctx - controllerUrl, authConfig, envKey
|
|
160
|
+
* @param {Object} [options] - `api` for injection, application and key search
|
|
161
|
+
* @returns {Promise<{ exitCode: number, rows: Array<Object> }>} Safe normalized rows
|
|
162
|
+
*/
|
|
163
|
+
async function runEnvSecretList(ctx, options = {}) {
|
|
164
|
+
const application = String(options.application || '').trim();
|
|
165
|
+
const api = options.api || { listEnvironmentConfiguration };
|
|
166
|
+
const raw = await api.listEnvironmentConfiguration(
|
|
167
|
+
ctx.controllerUrl, ctx.authConfig, ctx.envKey, application || undefined,
|
|
168
|
+
options.key ? { search: String(options.key).trim() } : {}
|
|
169
|
+
);
|
|
170
|
+
if (!Array.isArray(raw)) throw new Error('Configuration list response is not an array');
|
|
171
|
+
const rows = raw.map(row => normalizeRow(row, application)).sort((left, right) =>
|
|
172
|
+
left.application.localeCompare(right.application) || left.name.localeCompare(right.name)
|
|
173
|
+
);
|
|
174
|
+
const title = application ? `${application} (${ctx.envKey})` : ctx.envKey;
|
|
175
|
+
logger.log(sectionTitle(`Configuration: ${title}`));
|
|
176
|
+
logger.log(metadata(`Controller: ${ctx.controllerUrl}`));
|
|
177
|
+
logger.log('');
|
|
178
|
+
if (rows.length === 0) {
|
|
179
|
+
const suffix = options.key ? ` matching "${options.key}"` : '';
|
|
180
|
+
logger.log(metadata(` No configuration entries${suffix}.`));
|
|
181
|
+
return { exitCode: 0, rows };
|
|
182
|
+
}
|
|
183
|
+
const widths = tableWidths(rows);
|
|
184
|
+
tableHeader(!application, widths).forEach(line => logger.log(line));
|
|
185
|
+
rows.forEach(row => logger.log(formatRow(row, !application, widths)));
|
|
186
|
+
logger.log('');
|
|
187
|
+
logger.log(metadata(formatSummary(rows)));
|
|
188
|
+
return { exitCode: 0, rows };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
normalizeRow,
|
|
193
|
+
cleanCell,
|
|
194
|
+
formatCell,
|
|
195
|
+
tableWidths,
|
|
196
|
+
formatRow,
|
|
197
|
+
tableHeader,
|
|
198
|
+
formatSummary,
|
|
199
|
+
runEnvSecretList
|
|
200
|
+
};
|
|
@@ -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
|
+
};
|
package/lib/commands/login.js
CHANGED
|
@@ -121,6 +121,10 @@ async function normalizeControllerUrl(options) {
|
|
|
121
121
|
async function handleEnvironmentConfig(options) {
|
|
122
122
|
if (options.environment) {
|
|
123
123
|
const environment = options.environment.trim();
|
|
124
|
+
// `login -e tst` sets the environment without going through `auth set-environment`,
|
|
125
|
+
// so the mode rule has to be applied here too or it is trivially bypassed.
|
|
126
|
+
const { assertEnvironmentAllowedForCurrentMode } = require('../core/environment-mode-policy');
|
|
127
|
+
await assertEnvironmentAllowedForCurrentMode(environment);
|
|
124
128
|
await setCurrentEnvironment(environment);
|
|
125
129
|
if (!options.compact) {
|
|
126
130
|
logger.log(chalk.gray(`Environment: ${environment}`));
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
const { repairLegacyDatasource } = require('./repair-datasource-legacy');
|
|
12
|
+
const { repairKeyedDocument } = require('./repair-datasource-keyed-document');
|
|
11
13
|
const { repairOpenapiSection } = require('./repair-datasource-openapi');
|
|
12
14
|
const { repairSyncOperationsFromCapabilities } = require('./repair-datasource-sync-operations');
|
|
13
15
|
const { normalizeAutoRbacOperationKeys } = require('./repair-datasource-auto-rbac');
|
|
@@ -121,7 +123,8 @@ function applyOpenapiDatasourceRepairs(parsed, changes) {
|
|
|
121
123
|
*/
|
|
122
124
|
function runRepairDatasourceFile(parsed, options, changes, helpers) {
|
|
123
125
|
const out = Array.isArray(changes) ? changes : [];
|
|
124
|
-
let updated =
|
|
126
|
+
let updated = repairLegacyDatasource(parsed, out);
|
|
127
|
+
updated = helpers.stripDeprecatedServiceCategory(parsed, out) || updated;
|
|
125
128
|
|
|
126
129
|
if (shouldApplyServiceContractRepair(parsed, options)) {
|
|
127
130
|
updated = repairNoneLlmToServiceContract(parsed, out) || updated;
|
|
@@ -132,6 +135,7 @@ function runRepairDatasourceFile(parsed, options, changes, helpers) {
|
|
|
132
135
|
|
|
133
136
|
const none = helpers.isNoneEntityType(parsed?.entityType);
|
|
134
137
|
|
|
138
|
+
updated = repairKeyedDocument(parsed, options, out) || updated;
|
|
135
139
|
updated = applyBaseDatasourceRepairs(parsed, none, out, options, helpers) || updated;
|
|
136
140
|
updated = applyOpenapiDatasourceRepairs(parsed, out) || updated;
|
|
137
141
|
updated = applyOptionalDatasourceRepairs(parsed, options, none, out, helpers) || updated;
|