@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
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inquirer prompts for `aifabrix setup`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Setup prompt helpers:
|
|
5
5
|
* 1. {@link promptModeSelection} - Mode menu shown when infra is already up.
|
|
6
6
|
* 2. {@link promptAdminCredentials} - Admin email + password (fresh install only); email default
|
|
7
7
|
* from {@link module:lib/core/config.getAdminEmail} when `adminEmail` exists in config.yaml.
|
|
8
|
-
* 3. {@link promptAiTool} - AI tool provider + keys, only when the merged
|
|
9
|
-
* secret cascade (user-local + shared) does not already provide the keys.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
* resolution honors both user-local secrets and the shared `aifabrix-secrets`
|
|
13
|
-
* store (file or remote API) when configured.
|
|
14
|
-
*
|
|
15
|
-
* Writes use `setSecretInStore(...)` (primary user `secrets.local.yaml` only; shared is human via
|
|
16
|
-
* `aifabrix secret set --shared`) so the
|
|
17
|
-
* installer can write either to user-local secrets or to the shared store,
|
|
18
|
-
* depending on config.
|
|
19
|
-
*
|
|
20
|
-
* @fileoverview Setup prompts (mode menu, admin creds, AI tool)
|
|
9
|
+
* @fileoverview Setup prompts (mode menu, admin credentials, builder conflicts)
|
|
21
10
|
* @author AI Fabrix Team
|
|
22
11
|
* @version 2.0.0
|
|
23
12
|
*/
|
|
@@ -26,11 +15,6 @@
|
|
|
26
15
|
|
|
27
16
|
const path = require('path');
|
|
28
17
|
const inquirer = require('inquirer');
|
|
29
|
-
const chalk = require('chalk');
|
|
30
|
-
const logger = require('../utils/logger');
|
|
31
|
-
const secretsCore = require('../core/secrets');
|
|
32
|
-
const secretsEnsure = require('../core/secrets-ensure');
|
|
33
|
-
const { formatSuccessLine, infoLine } = require('../utils/cli-test-layout-chalk');
|
|
34
18
|
const { validateEmail, validatePassword } = require('./setup-prompts-validators');
|
|
35
19
|
const {
|
|
36
20
|
promptAdminCredentialsWithProfile,
|
|
@@ -45,13 +29,6 @@ const MODE = Object.freeze({
|
|
|
45
29
|
UPDATE_IMAGES: 'update-images'
|
|
46
30
|
});
|
|
47
31
|
|
|
48
|
-
/** AI tool key names in `secrets.local.yaml` / shared secrets. */
|
|
49
|
-
const AI_KEYS = Object.freeze({
|
|
50
|
-
OPENAI_API_KEY: 'secrets-openaiApiKeyVault',
|
|
51
|
-
AZURE_OPENAI_URL: 'azure-openaiapi-urlKeyVault',
|
|
52
|
-
AZURE_OPENAI_API_KEY: 'secrets-azureOpenaiApiKeyVault'
|
|
53
|
-
});
|
|
54
|
-
|
|
55
32
|
/** Builder folder conflict actions (setup preflight). */
|
|
56
33
|
const BUILDER_DIR_ACTION = Object.freeze({
|
|
57
34
|
KEEP: 'keep',
|
|
@@ -131,158 +108,6 @@ async function promptAdminCredentials(options = {}) {
|
|
|
131
108
|
};
|
|
132
109
|
}
|
|
133
110
|
|
|
134
|
-
/**
|
|
135
|
-
* @typedef {Object} AiToolStatus
|
|
136
|
-
* @property {boolean} openAiConfigured - True when OpenAI key resolves non-empty
|
|
137
|
-
* @property {boolean} azureOpenAiConfigured - True when Azure URL + key resolve non-empty
|
|
138
|
-
*/
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Detect whether AI tool keys are already present in the merged secret view.
|
|
142
|
-
* Treats values surrounded by Handlebars-style placeholders (e.g. `{{...}}`)
|
|
143
|
-
* as not configured.
|
|
144
|
-
*
|
|
145
|
-
* @async
|
|
146
|
-
* @returns {Promise<AiToolStatus>}
|
|
147
|
-
*/
|
|
148
|
-
async function detectAiToolStatus() {
|
|
149
|
-
let merged = {};
|
|
150
|
-
try {
|
|
151
|
-
merged = await secretsCore.loadSecrets(undefined);
|
|
152
|
-
} catch {
|
|
153
|
-
merged = {};
|
|
154
|
-
}
|
|
155
|
-
const isSet = (key) => {
|
|
156
|
-
const raw = merged ? merged[key] : undefined;
|
|
157
|
-
if (raw === undefined || raw === null) return false;
|
|
158
|
-
const value = String(raw).trim();
|
|
159
|
-
if (!value) return false;
|
|
160
|
-
if (value.startsWith('{{') && value.endsWith('}}')) return false;
|
|
161
|
-
return true;
|
|
162
|
-
};
|
|
163
|
-
return {
|
|
164
|
-
openAiConfigured: isSet(AI_KEYS.OPENAI_API_KEY),
|
|
165
|
-
azureOpenAiConfigured:
|
|
166
|
-
isSet(AI_KEYS.AZURE_OPENAI_URL) && isSet(AI_KEYS.AZURE_OPENAI_API_KEY)
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Ask which AI tool the user wants to configure when none is configured yet.
|
|
172
|
-
* @async
|
|
173
|
-
* @returns {Promise<'openai'|'azure'|'skip'>}
|
|
174
|
-
*/
|
|
175
|
-
async function askAiToolChoice() {
|
|
176
|
-
const { choice } = await inquirer.prompt([
|
|
177
|
-
{
|
|
178
|
-
type: 'list',
|
|
179
|
-
name: 'choice',
|
|
180
|
-
message: 'Which AI tool do you want to configure for the dataplane?',
|
|
181
|
-
choices: [
|
|
182
|
-
{ name: 'OpenAI (api.openai.com)', value: 'openai' },
|
|
183
|
-
{ name: 'Azure OpenAI (your Azure resource)', value: 'azure' },
|
|
184
|
-
{ name: 'Skip for now (set later via aifabrix secret set)', value: 'skip' }
|
|
185
|
-
]
|
|
186
|
-
}
|
|
187
|
-
]);
|
|
188
|
-
return choice;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Collect and persist OpenAI key.
|
|
193
|
-
* @async
|
|
194
|
-
* @returns {Promise<void>}
|
|
195
|
-
*/
|
|
196
|
-
async function collectAndSaveOpenAiKey() {
|
|
197
|
-
const { apiKey } = await inquirer.prompt([
|
|
198
|
-
{
|
|
199
|
-
type: 'password',
|
|
200
|
-
name: 'apiKey',
|
|
201
|
-
message: 'OpenAI API key:',
|
|
202
|
-
mask: '*',
|
|
203
|
-
validate(input) {
|
|
204
|
-
return String(input || '').trim() ? true : 'API key is required';
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
]);
|
|
208
|
-
await secretsEnsure.setSecretInStore(AI_KEYS.OPENAI_API_KEY, String(apiKey).trim());
|
|
209
|
-
logger.log(formatSuccessLine('OpenAI API key saved'));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Collect and persist Azure OpenAI URL + key.
|
|
214
|
-
* @async
|
|
215
|
-
* @returns {Promise<void>}
|
|
216
|
-
*/
|
|
217
|
-
async function collectAndSaveAzureOpenAi() {
|
|
218
|
-
const answers = await inquirer.prompt([
|
|
219
|
-
{
|
|
220
|
-
type: 'input',
|
|
221
|
-
name: 'url',
|
|
222
|
-
message: 'Azure OpenAI endpoint URL (e.g. https://my-aoai.openai.azure.com):',
|
|
223
|
-
validate(input) {
|
|
224
|
-
const value = String(input || '').trim();
|
|
225
|
-
if (!value) return 'Endpoint URL is required';
|
|
226
|
-
if (!/^https?:\/\//.test(value)) return 'URL must start with http(s)://';
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
type: 'password',
|
|
232
|
-
name: 'apiKey',
|
|
233
|
-
message: 'Azure OpenAI API key:',
|
|
234
|
-
mask: '*',
|
|
235
|
-
validate(input) {
|
|
236
|
-
return String(input || '').trim() ? true : 'API key is required';
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
]);
|
|
240
|
-
await secretsEnsure.setSecretInStore(AI_KEYS.AZURE_OPENAI_URL, String(answers.url).trim());
|
|
241
|
-
await secretsEnsure.setSecretInStore(AI_KEYS.AZURE_OPENAI_API_KEY, String(answers.apiKey).trim());
|
|
242
|
-
logger.log(formatSuccessLine('Azure OpenAI endpoint and API key saved'));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Run the AI tool prompt sequence.
|
|
247
|
-
* Skips silently when either provider is already configured (in any source).
|
|
248
|
-
*
|
|
249
|
-
* @async
|
|
250
|
-
* @param {{ silentIfConfigured?: boolean }} [options]
|
|
251
|
-
* @returns {Promise<void>}
|
|
252
|
-
*/
|
|
253
|
-
async function promptAiTool(options = {}) {
|
|
254
|
-
const silentIfConfigured = options && options.silentIfConfigured === true;
|
|
255
|
-
const status = await detectAiToolStatus();
|
|
256
|
-
if (status.openAiConfigured) {
|
|
257
|
-
if (!silentIfConfigured) {
|
|
258
|
-
logger.log(infoLine('OpenAI key already configured - skipping AI tool prompt.'));
|
|
259
|
-
}
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
if (status.azureOpenAiConfigured) {
|
|
263
|
-
if (!silentIfConfigured) {
|
|
264
|
-
logger.log(infoLine('Azure OpenAI endpoint and key already configured - skipping AI tool prompt.'));
|
|
265
|
-
}
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
const choice = await askAiToolChoice();
|
|
269
|
-
if (choice === 'openai') {
|
|
270
|
-
await collectAndSaveOpenAiKey();
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (choice === 'azure') {
|
|
274
|
-
await collectAndSaveAzureOpenAi();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
logger.log(
|
|
278
|
-
chalk.gray(
|
|
279
|
-
'Skipped. Set later: aifabrix secret set ' +
|
|
280
|
-
AI_KEYS.OPENAI_API_KEY +
|
|
281
|
-
' <key> (or the two azure-openaiapi-urlKeyVault / secrets-azureOpenaiApiKeyVault keys)'
|
|
282
|
-
)
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
111
|
/**
|
|
287
112
|
* Absolute directory paths (with trailing sep) shown when setup will replace platform apps.
|
|
288
113
|
*
|
|
@@ -353,7 +178,6 @@ async function promptBuilderDirConflict(info) {
|
|
|
353
178
|
|
|
354
179
|
module.exports = {
|
|
355
180
|
MODE,
|
|
356
|
-
AI_KEYS,
|
|
357
181
|
BUILDER_DIR_ACTION,
|
|
358
182
|
INSTALLATION_PROFILE,
|
|
359
183
|
formatBuilderPlatformReplaceLines,
|
|
@@ -361,8 +185,6 @@ module.exports = {
|
|
|
361
185
|
confirmDestructiveMode,
|
|
362
186
|
promptInstallationProfile,
|
|
363
187
|
promptAdminCredentials,
|
|
364
|
-
promptAiTool,
|
|
365
|
-
detectAiToolStatus,
|
|
366
188
|
promptBuilderDirConflict,
|
|
367
189
|
validateEmail,
|
|
368
190
|
validatePassword
|
|
@@ -105,7 +105,8 @@ function shouldSkipE2eForStaticPackage(systemKey, options) {
|
|
|
105
105
|
if (storage.length === 0) {
|
|
106
106
|
return false;
|
|
107
107
|
}
|
|
108
|
-
return storage.every((ds) =>
|
|
108
|
+
return storage.every((ds) => !isKeyedDocumentDatasource(ds) &&
|
|
109
|
+
String(ds.syncBridge || 'disabled').toLowerCase() === 'disabled');
|
|
109
110
|
} catch {
|
|
110
111
|
return false;
|
|
111
112
|
}
|
|
@@ -124,7 +125,30 @@ function shouldSkipOperationsE2e(systemKey, options) {
|
|
|
124
125
|
|| shouldSkipE2eForStaticPackage(systemKey, options);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
/** @param {Object} ds @returns {boolean} */
|
|
129
|
+
function isKeyedDocumentDatasource(ds) {
|
|
130
|
+
const ops = ds?.execution?.cip?.operations;
|
|
131
|
+
return ds?.entityType === 'documentStorage' && !ops?.list &&
|
|
132
|
+
!ds.openapi?.operations?.list && !ds.capabilities?.some(cap => (cap.key || cap) === 'list') &&
|
|
133
|
+
Array.isArray(ops?.get?.steps) && ops.get.steps.some(step => step?.output?.mode === 'documents');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** @param {string} systemKey @param {Object} options @returns {string[]} */
|
|
137
|
+
function keyedDocumentDatasourceKeys(systemKey, options) {
|
|
138
|
+
if (options.skipE2e === true || options.minimal === true) return [];
|
|
139
|
+
try {
|
|
140
|
+
const { resolveAppWorkspace } = require('../utils/app-path-resolve');
|
|
141
|
+
const { appPath } = resolveAppWorkspace(systemKey, options);
|
|
142
|
+
const storage = loadStorageDatasources(appPath);
|
|
143
|
+
return storage.length && storage.every(isKeyedDocumentDatasource) ? storage.map(ds => ds.key) : [];
|
|
144
|
+
} catch {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
127
149
|
module.exports = {
|
|
150
|
+
isKeyedDocumentDatasource,
|
|
151
|
+
keyedDocumentDatasourceKeys,
|
|
128
152
|
shouldSkipOperationsE2e,
|
|
129
153
|
shouldSkipE2eForRoleAssistant,
|
|
130
154
|
shouldSkipE2eForStaticPackage
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
+
const { keyedDocumentDatasourceKeys } = require('./verify-operations-skip-e2e');
|
|
8
|
+
const { hasAllKeyedDocumentPersistence } = require('../utils/datasource-binary-evidence');
|
|
9
|
+
|
|
7
10
|
const { syncLocalIfRequested } = require('./test-e2e-external');
|
|
8
11
|
const { cliOptsSkipPublish } = require('../utils/cli-sync-options');
|
|
9
12
|
const { buildOperationsVerifyStepPlan } = require('./verify-operations-step-plan');
|
|
@@ -99,10 +102,19 @@ async function runOperationsVerifySteps(systemKey, options) {
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
const keyedKeys = keyedDocumentDatasourceKeys(systemKey, options);
|
|
102
106
|
const e2eLabel = STEP_PROGRESS.e2e;
|
|
103
107
|
stepResults.e2e = await runWithVerifyStepProgress(
|
|
104
108
|
e2eLabel,
|
|
105
|
-
async(progress) =>
|
|
109
|
+
async(progress) => {
|
|
110
|
+
const result = await plan.e2e.run(progress);
|
|
111
|
+
if (keyedKeys.length) {
|
|
112
|
+
stepResults.keyedDocumentGet = true;
|
|
113
|
+
stepResults.documentPersistence = hasAllKeyedDocumentPersistence(result, keyedKeys);
|
|
114
|
+
return plan.e2e.ok(result) && stepResults.documentPersistence;
|
|
115
|
+
}
|
|
116
|
+
return plan.e2e.ok(result);
|
|
117
|
+
},
|
|
106
118
|
options
|
|
107
119
|
);
|
|
108
120
|
return { stepResults, hardFailure: !stepResults.e2e, stoppedEarly: false };
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const { repairKeyedDocument } = require('./repair-datasource-keyed-document');
|
|
8
|
+
|
|
7
9
|
const ENTITY_TYPE_FALLBACK = 'record-storage';
|
|
8
10
|
const VALID_ENTITY_TYPES = new Set([
|
|
9
11
|
'document-storage',
|
|
@@ -56,7 +58,7 @@ function normalizeCipOutputModes(datasourceConfig) {
|
|
|
56
58
|
for (const operation of Object.values(operations)) {
|
|
57
59
|
if (!operation || !Array.isArray(operation.steps)) continue;
|
|
58
60
|
for (const step of operation.steps) {
|
|
59
|
-
if (step?.output?.mode && step.output.mode
|
|
61
|
+
if (step?.output?.mode && !['records', 'documents'].includes(step.output.mode)) {
|
|
60
62
|
step.output.mode = 'records';
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -80,7 +82,7 @@ function normalizeWizardTestPayload(datasourceConfig) {
|
|
|
80
82
|
* @param {Object} datasourceConfig - Datasource config
|
|
81
83
|
* @returns {Object} Normalized config
|
|
82
84
|
*/
|
|
83
|
-
function normalizeDatasourceConfig(datasourceConfig) {
|
|
85
|
+
function normalizeDatasourceConfig(datasourceConfig, openapiSpec) {
|
|
84
86
|
if (!datasourceConfig || typeof datasourceConfig !== 'object') {
|
|
85
87
|
return datasourceConfig;
|
|
86
88
|
}
|
|
@@ -89,6 +91,7 @@ function normalizeDatasourceConfig(datasourceConfig) {
|
|
|
89
91
|
}
|
|
90
92
|
normalizePortalInput(datasourceConfig);
|
|
91
93
|
normalizeCipOutputModes(datasourceConfig);
|
|
94
|
+
repairKeyedDocument(datasourceConfig, { openapiSpec });
|
|
92
95
|
normalizeWizardTestPayload(datasourceConfig);
|
|
93
96
|
return datasourceConfig;
|
|
94
97
|
}
|
|
@@ -100,10 +103,10 @@ function normalizeDatasourceConfig(datasourceConfig) {
|
|
|
100
103
|
* @param {Object|Object[]} datasourceConfigs - Datasource config(s)
|
|
101
104
|
* @returns {{ systemConfig: Object, datasourceConfigs: Object[] }} Normalized configs
|
|
102
105
|
*/
|
|
103
|
-
function normalizeWizardConfigs(systemConfig, datasourceConfigs) {
|
|
106
|
+
function normalizeWizardConfigs(systemConfig, datasourceConfigs, openapiSpec) {
|
|
104
107
|
const normalizedSystem = normalizeSystemConfig(systemConfig);
|
|
105
108
|
const configs = Array.isArray(datasourceConfigs) ? datasourceConfigs : [datasourceConfigs];
|
|
106
|
-
const normalizedDatasources = configs.map(normalizeDatasourceConfig);
|
|
109
|
+
const normalizedDatasources = configs.map(config => normalizeDatasourceConfig(config, openapiSpec));
|
|
107
110
|
return { systemConfig: normalizedSystem, datasourceConfigs: normalizedDatasources };
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -3,28 +3,24 @@
|
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
|
-
/* eslint-disable max-lines */
|
|
7
6
|
|
|
7
|
+
const path = require('path');
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const ora = require('ora');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const fs = require('fs').promises;
|
|
12
10
|
const logger = require('../utils/logger');
|
|
13
11
|
const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
14
12
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
15
13
|
const { normalizeWizardConfigs } = require('./wizard-config-normalizer');
|
|
16
|
-
const { formatSuccessLine
|
|
14
|
+
const { formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
17
15
|
const {
|
|
18
16
|
createWizardSession,
|
|
19
17
|
updateWizardSession,
|
|
20
18
|
detectType,
|
|
21
19
|
getPlatformConfig,
|
|
22
20
|
generateConfig,
|
|
23
|
-
validateWizardConfig
|
|
24
|
-
getDeploymentDocs,
|
|
25
|
-
postDeploymentDocs
|
|
21
|
+
validateWizardConfig
|
|
26
22
|
} = require('../api/wizard.api');
|
|
27
|
-
const {
|
|
23
|
+
const { handleFileSaving, validateAndCheckAppDirectory } = require('./wizard-file-saving');
|
|
28
24
|
const {
|
|
29
25
|
parseOpenApiSource,
|
|
30
26
|
testMcpServerConnection,
|
|
@@ -44,38 +40,6 @@ const {
|
|
|
44
40
|
const { validateDatasourceKeysBeforePlatformConfig } = require('../validation/validate-datasource-keys-api');
|
|
45
41
|
const { handleEntitySelection } = require('./wizard-entity-selection');
|
|
46
42
|
|
|
47
|
-
/**
|
|
48
|
-
* Validate app name and check if directory exists
|
|
49
|
-
* @async
|
|
50
|
-
* @function validateAndCheckAppDirectory
|
|
51
|
-
* @param {string} appName - Application name
|
|
52
|
-
* @param {boolean} [interactive=true] - Whether to prompt for confirmation
|
|
53
|
-
* @returns {Promise<boolean>} True if should continue, false if cancelled
|
|
54
|
-
*/
|
|
55
|
-
async function validateAndCheckAppDirectory(appName, interactive = true) {
|
|
56
|
-
if (!/^[a-z0-9-_]+$/.test(appName)) {
|
|
57
|
-
throw new Error('Application name must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
58
|
-
}
|
|
59
|
-
const appPath = path.join(process.cwd(), 'integration', appName);
|
|
60
|
-
try {
|
|
61
|
-
await fs.access(appPath);
|
|
62
|
-
if (interactive) {
|
|
63
|
-
const { overwrite } = await require('inquirer').prompt([{
|
|
64
|
-
type: 'confirm', name: 'overwrite',
|
|
65
|
-
message: `Directory ${appPath} already exists. Overwrite?`, default: false
|
|
66
|
-
}]);
|
|
67
|
-
if (!overwrite) {
|
|
68
|
-
logger.log(chalk.yellow('Wizard cancelled.')); return false;
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
logger.log(chalk.yellow(`Warning: Directory ${appPath} exists. Overwriting...`));
|
|
72
|
-
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (error.code !== 'ENOENT') throw error;
|
|
75
|
-
}
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
43
|
/**
|
|
80
44
|
* Extract session ID from response data
|
|
81
45
|
* @function extractSessionId
|
|
@@ -393,7 +357,7 @@ async function handleConfigurationGeneration(dataplaneUrl, authConfig, options)
|
|
|
393
357
|
}
|
|
394
358
|
const result = extractConfigurationFromResponse(generateResponse);
|
|
395
359
|
const systemConfig = resolveSystemConfigForWizardMode(result.systemConfig, options);
|
|
396
|
-
const normalized = normalizeWizardConfigs(systemConfig, result.datasourceConfigs);
|
|
360
|
+
const normalized = normalizeWizardConfigs(systemConfig, result.datasourceConfigs, options.openapiSpec);
|
|
397
361
|
await maybeWriteWizardDebugLog(generateResponse, prefs, options);
|
|
398
362
|
logger.log(formatSuccessLine('Configuration generated successfully'));
|
|
399
363
|
return {
|
|
@@ -446,122 +410,6 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
|
|
|
446
410
|
}
|
|
447
411
|
}
|
|
448
412
|
|
|
449
|
-
/**
|
|
450
|
-
* Fetches deployment docs and writes README.md when application config and deploy JSON are available.
|
|
451
|
-
* @async
|
|
452
|
-
* @param {string} appPath - Application path
|
|
453
|
-
* @param {string} appName - Application name
|
|
454
|
-
* @param {string} dataplaneUrl - Dataplane URL
|
|
455
|
-
* @param {Object} authConfig - Authentication configuration
|
|
456
|
-
* @param {string} systemKey - System key
|
|
457
|
-
*/
|
|
458
|
-
async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl, authConfig, systemKey) {
|
|
459
|
-
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
460
|
-
const deployPath = path.join(appPath, `${appName}-deploy.json`);
|
|
461
|
-
let variablesYaml = null;
|
|
462
|
-
let deployJson = null;
|
|
463
|
-
try {
|
|
464
|
-
const configPath = resolveApplicationConfigPath(appPath);
|
|
465
|
-
variablesYaml = await fs.readFile(configPath, 'utf8');
|
|
466
|
-
} catch {
|
|
467
|
-
// optional
|
|
468
|
-
}
|
|
469
|
-
try {
|
|
470
|
-
const deployContent = await fs.readFile(deployPath, 'utf8');
|
|
471
|
-
deployJson = JSON.parse(deployContent);
|
|
472
|
-
} catch {
|
|
473
|
-
// optional
|
|
474
|
-
}
|
|
475
|
-
const hasBody = variablesYaml !== null || deployJson !== null;
|
|
476
|
-
const body = hasBody ? { variablesYaml: variablesYaml || null, deployJson: deployJson || null } : null;
|
|
477
|
-
const docsResponse = body
|
|
478
|
-
? await postDeploymentDocs(dataplaneUrl, authConfig, systemKey, body)
|
|
479
|
-
: await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
|
|
480
|
-
const content = docsResponse?.data?.content ?? docsResponse?.content;
|
|
481
|
-
// Only overwrite README when the API returns substantial content; otherwise keep the template README
|
|
482
|
-
const MIN_README_LENGTH = 400;
|
|
483
|
-
if (content && typeof content === 'string' && content.trim().length >= MIN_README_LENGTH) {
|
|
484
|
-
const readmePath = path.join(appPath, 'README.md');
|
|
485
|
-
await fs.writeFile(readmePath, content, 'utf8');
|
|
486
|
-
logger.log(chalk.gray(' Updated README.md from deployment-docs API (application config + deploy JSON).'));
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Writes rbac.yaml / rbac.json from datasource resourceType + capabilities (same as `af repair --rbac`).
|
|
492
|
-
* @param {Object} generatedFiles - Result of generateWizardFiles (appPath, systemFilePath, datasourceFilePaths)
|
|
493
|
-
* @param {string} format - Project format: yaml | json
|
|
494
|
-
*/
|
|
495
|
-
function logWizardFileSaveFooter(appName, generatedFiles) {
|
|
496
|
-
logger.log(formatSuccessParagraph('Wizard completed successfully!'));
|
|
497
|
-
logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
|
|
498
|
-
logger.log(chalk.blue('\nNext steps:'));
|
|
499
|
-
logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
|
|
500
|
-
logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
|
|
501
|
-
logger.log(chalk.gray(` 3. Deploy using: node deploy.js or aifabrix deploy ${appName}`));
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function mergeRbacAfterWizardFilesWritten(generatedFiles, format) {
|
|
505
|
-
const { mergeRbacFromDatasources, extractRbacFromSystem } = require('./repair-rbac');
|
|
506
|
-
const { loadConfigFile } = require('../utils/config-format');
|
|
507
|
-
const systemParsedForRbac = loadConfigFile(generatedFiles.systemFilePath);
|
|
508
|
-
const datasourceFileNames = (generatedFiles.datasourceFilePaths || []).map((p) => path.basename(p));
|
|
509
|
-
const rbacChanges = [];
|
|
510
|
-
const rbacUpdated = mergeRbacFromDatasources(
|
|
511
|
-
generatedFiles.appPath,
|
|
512
|
-
systemParsedForRbac,
|
|
513
|
-
datasourceFileNames,
|
|
514
|
-
extractRbacFromSystem,
|
|
515
|
-
{ format: format === 'json' ? 'json' : 'yaml', dryRun: false, changes: rbacChanges }
|
|
516
|
-
);
|
|
517
|
-
if (rbacUpdated && rbacChanges.length) {
|
|
518
|
-
rbacChanges.forEach((c) => logger.log(chalk.gray(` RBAC: ${c}`)));
|
|
519
|
-
logger.log(chalk.green(' RBAC file updated from datasource capabilities (enableRBAC).'));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Handle file saving step
|
|
525
|
-
* @async
|
|
526
|
-
* @function handleFileSaving
|
|
527
|
-
* @param {string} appName - Application name
|
|
528
|
-
* @param {Object} systemConfig - System configuration
|
|
529
|
-
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
530
|
-
* @param {string} systemKey - System key
|
|
531
|
-
* @param {{ dataplaneUrl: string, authConfig: Object, enableRBAC?: boolean }} ctx - Dataplane auth + optional RBAC generation
|
|
532
|
-
* @returns {Promise<Object>} Generated files information
|
|
533
|
-
*/
|
|
534
|
-
async function handleFileSaving(appName, systemConfig, datasourceConfigs, systemKey, ctx) {
|
|
535
|
-
const { dataplaneUrl, authConfig, enableRBAC = false } = ctx || {};
|
|
536
|
-
logger.log(chalk.blue('\n\uD83D\uDCCB Step 7: Save Files'));
|
|
537
|
-
const spinner = ora('Saving files...').start();
|
|
538
|
-
try {
|
|
539
|
-
const config = require('../core/config');
|
|
540
|
-
const format = ctx?.format || (await config.getFormat()) || 'yaml';
|
|
541
|
-
const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme: null, format });
|
|
542
|
-
if (enableRBAC && generatedFiles.appPath && generatedFiles.systemFilePath) {
|
|
543
|
-
try {
|
|
544
|
-
mergeRbacAfterWizardFilesWritten(generatedFiles, format);
|
|
545
|
-
} catch (e) {
|
|
546
|
-
logger.log(chalk.yellow(` Could not generate RBAC file: ${e.message}`));
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
if (systemKey && dataplaneUrl && authConfig && generatedFiles.appPath) {
|
|
550
|
-
try {
|
|
551
|
-
await tryUpdateReadmeFromDeploymentDocs(generatedFiles.appPath, appName, dataplaneUrl, authConfig, systemKey);
|
|
552
|
-
} catch (e) {
|
|
553
|
-
logger.log(chalk.gray(` Could not fetch AI-generated README: ${e.message}`));
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
spinner.stop();
|
|
557
|
-
logWizardFileSaveFooter(appName, generatedFiles);
|
|
558
|
-
return generatedFiles;
|
|
559
|
-
} catch (error) {
|
|
560
|
-
spinner.stop();
|
|
561
|
-
throw error;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
413
|
/**
|
|
566
414
|
* Setup dataplane URL and authentication
|
|
567
415
|
* @async
|