@aifabrix/builder 2.44.4 → 2.44.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cli-layout.mdc +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +68 -17
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/types/wizard.types.js +2 -1
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-app.test-commands.js +9 -5
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +138 -61
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility.js +97 -33
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +225 -19
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -354
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/test-e2e-external.js +4 -3
- package/lib/commands/up-common.js +97 -12
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +58 -15
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +72 -14
- package/lib/commands/wizard-headless.js +7 -3
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +210 -61
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +4 -1
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- package/lib/schema/wizard-config.schema.json +2 -2
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/external-readme.js +117 -4
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +73 -20
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +7 -7
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +89 -102
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
registerHandlebarsHelper
|
|
27
27
|
} = require('./helpers');
|
|
28
28
|
const secretsEnsure = require('../core/secrets-ensure');
|
|
29
|
+
const { getRestartableInfraServiceNames } = require('../constants/infra-compose-service-names');
|
|
29
30
|
const {
|
|
30
31
|
buildTraefikConfig,
|
|
31
32
|
validateTraefikConfig,
|
|
@@ -36,6 +37,7 @@ const {
|
|
|
36
37
|
startDockerServicesAndConfigure,
|
|
37
38
|
checkInfraHealth
|
|
38
39
|
} = require('./services');
|
|
40
|
+
const { tryComposeProjectDown, stopInfraDockerStackOrphaned } = require('./orphan-infra-docker-teardown');
|
|
39
41
|
const adminSecrets = require('../core/admin-secrets');
|
|
40
42
|
// Lazy require to avoid circular dependency: infra -> app/down -> run-helpers -> infra
|
|
41
43
|
|
|
@@ -220,9 +222,18 @@ async function stopInfra() {
|
|
|
220
222
|
const devId = await config.getDeveloperId();
|
|
221
223
|
const { infraDir, adminSecretsPath } = resolveInfraStatePaths(devId);
|
|
222
224
|
const composePath = path.join(infraDir, 'compose.yaml');
|
|
225
|
+
const hasCompose = fs.existsSync(composePath);
|
|
226
|
+
const hasAdminSecrets = fs.existsSync(adminSecretsPath);
|
|
223
227
|
|
|
224
|
-
if (!
|
|
225
|
-
logger.log('
|
|
228
|
+
if (!hasCompose || !hasAdminSecrets) {
|
|
229
|
+
logger.log('Infra compose or admin-secrets missing; stopping Docker stack (no volume delete)...');
|
|
230
|
+
await stopAllAppContainers(devId);
|
|
231
|
+
try {
|
|
232
|
+
await tryComposeProjectDown(devId, false);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.log(`Compose project down failed (${err.message}); applying orphan cleanup...`);
|
|
235
|
+
await stopInfraDockerStackOrphaned(devId, { removeVolumes: false });
|
|
236
|
+
}
|
|
226
237
|
return;
|
|
227
238
|
}
|
|
228
239
|
|
|
@@ -281,21 +292,34 @@ async function stopInfraWithVolumes() {
|
|
|
281
292
|
const devId = await config.getDeveloperId();
|
|
282
293
|
const { infraDir, adminSecretsPath } = resolveInfraStatePaths(devId);
|
|
283
294
|
const composePath = path.join(infraDir, 'compose.yaml');
|
|
295
|
+
const hasCompose = fs.existsSync(composePath);
|
|
296
|
+
const hasAdminSecrets = fs.existsSync(adminSecretsPath);
|
|
284
297
|
|
|
285
|
-
|
|
286
|
-
|
|
298
|
+
await stopAllAppContainersAndVolumes(devId);
|
|
299
|
+
|
|
300
|
+
if (hasCompose && hasAdminSecrets) {
|
|
301
|
+
await withRunEnv(infraDir, adminSecretsPath, async(runEnvPath) => {
|
|
302
|
+
logger.log('Stopping infrastructure services and removing all data...');
|
|
303
|
+
const projectName = getInfraProjectName(devId);
|
|
304
|
+
const composeCmd = await dockerUtils.getComposeCommand();
|
|
305
|
+
try {
|
|
306
|
+
await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${runEnvPath}" down -v`, { cwd: infraDir });
|
|
307
|
+
} catch (err) {
|
|
308
|
+
logger.log(`Compose down failed (${err.message}); applying orphan cleanup...`);
|
|
309
|
+
await stopInfraDockerStackOrphaned(devId, { removeVolumes: true });
|
|
310
|
+
}
|
|
311
|
+
logger.log('Infrastructure services stopped and all data removed');
|
|
312
|
+
});
|
|
287
313
|
return;
|
|
288
314
|
}
|
|
289
315
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
await
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
logger.log('Infrastructure services stopped and all data removed');
|
|
298
|
-
});
|
|
316
|
+
logger.log('Infra compose or admin-secrets missing; tearing down stuck Docker stack (project/volumes/network)...');
|
|
317
|
+
try {
|
|
318
|
+
await tryComposeProjectDown(devId, true);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
logger.log(`Compose project down failed (${err.message}); applying orphan cleanup...`);
|
|
321
|
+
await stopInfraDockerStackOrphaned(devId, { removeVolumes: true });
|
|
322
|
+
}
|
|
299
323
|
}
|
|
300
324
|
|
|
301
325
|
/**
|
|
@@ -305,18 +329,19 @@ async function stopInfraWithVolumes() {
|
|
|
305
329
|
* @async
|
|
306
330
|
* @function restartService
|
|
307
331
|
* @param {string} serviceName - Name of service to restart
|
|
332
|
+
* @param {{ suppressProgressLog?: boolean }} [options] - When `suppressProgressLog: true`, skip internal logger lines (CLI prints layout).
|
|
308
333
|
* @returns {Promise<void>} Resolves when service is restarted
|
|
309
334
|
* @throws {Error} If service doesn't exist or restart fails
|
|
310
335
|
*
|
|
311
336
|
* @example
|
|
312
|
-
* await restartService('
|
|
313
|
-
* //
|
|
337
|
+
* await restartService('postgres');
|
|
338
|
+
* // Postgres service is restarted
|
|
314
339
|
*/
|
|
315
|
-
async function restartService(serviceName) {
|
|
340
|
+
async function restartService(serviceName, options = {}) {
|
|
316
341
|
if (!serviceName || typeof serviceName !== 'string') {
|
|
317
342
|
throw new Error('Service name is required and must be a string');
|
|
318
343
|
}
|
|
319
|
-
const validServices =
|
|
344
|
+
const validServices = getRestartableInfraServiceNames();
|
|
320
345
|
if (!validServices.includes(serviceName)) {
|
|
321
346
|
throw new Error(`Invalid service name. Must be one of: ${validServices.join(', ')}`);
|
|
322
347
|
}
|
|
@@ -329,12 +354,17 @@ async function restartService(serviceName) {
|
|
|
329
354
|
throw new Error('Infrastructure not properly configured');
|
|
330
355
|
}
|
|
331
356
|
|
|
357
|
+
const suppressLog = Boolean(options && options.suppressProgressLog);
|
|
332
358
|
await withRunEnv(infraDir, adminSecretsPath, async(runEnvPath) => {
|
|
333
|
-
|
|
359
|
+
if (!suppressLog) {
|
|
360
|
+
logger.log(`Restarting ${serviceName} service...`);
|
|
361
|
+
}
|
|
334
362
|
const projectName = getInfraProjectName(devId);
|
|
335
363
|
const composeCmd = await dockerUtils.getComposeCommand();
|
|
336
364
|
await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${runEnvPath}" restart ${serviceName}`, { cwd: infraDir });
|
|
337
|
-
|
|
365
|
+
if (!suppressLog) {
|
|
366
|
+
logger.log(`${serviceName} service restarted successfully`);
|
|
367
|
+
}
|
|
338
368
|
});
|
|
339
369
|
}
|
|
340
370
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tear down AI Fabrix infra Docker resources when compose.yaml / admin-secrets
|
|
3
|
+
* are missing from disk (e.g. after manual deletes). Matches compose naming from
|
|
4
|
+
* `templates/infra/compose.yaml.hbs` and `lib/infrastructure/compose.js`.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Orphan Docker teardown for developer-scoped infra stacks
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const dockerUtils = require('../utils/docker');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { execAsyncWithCwd } = require('./services');
|
|
14
|
+
const { getInfraProjectName } = require('./helpers');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Bridge network name for this developer (must match compose generator).
|
|
18
|
+
* @param {string|number} devId - Developer ID
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function getInfraBridgeNetworkName(devId) {
|
|
22
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
23
|
+
return idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Known infra service container names (postgres, redis, optional UIs).
|
|
28
|
+
* @param {string|number} devId - Developer ID
|
|
29
|
+
* @returns {string[]}
|
|
30
|
+
*/
|
|
31
|
+
function getInfraServiceContainerNames(devId) {
|
|
32
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
33
|
+
if (idNum === 0) {
|
|
34
|
+
return ['aifabrix-postgres', 'aifabrix-redis', 'aifabrix-pgadmin', 'aifabrix-redis-commander', 'aifabrix-traefik'];
|
|
35
|
+
}
|
|
36
|
+
return [
|
|
37
|
+
`aifabrix-dev${devId}-postgres`,
|
|
38
|
+
`aifabrix-dev${devId}-redis`,
|
|
39
|
+
`aifabrix-dev${devId}-pgadmin`,
|
|
40
|
+
`aifabrix-dev${devId}-redis-commander`,
|
|
41
|
+
`aifabrix-dev${devId}-traefik`
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Named volumes declared in infra compose (explicit `name:` entries).
|
|
47
|
+
* @param {string|number} devId - Developer ID
|
|
48
|
+
* @returns {string[]}
|
|
49
|
+
*/
|
|
50
|
+
function getInfraNamedVolumeCandidates(devId) {
|
|
51
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
52
|
+
if (idNum === 0) {
|
|
53
|
+
return ['infra_postgres_data', 'infra_redis_data', 'infra_pgadmin_data'];
|
|
54
|
+
}
|
|
55
|
+
return [
|
|
56
|
+
`infra_dev${devId}_postgres_data`,
|
|
57
|
+
`infra_dev${devId}_redis_data`,
|
|
58
|
+
`infra_dev${devId}_pgadmin_data`
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `docker compose down` using only the Compose project name (works when the
|
|
64
|
+
* compose file was removed but the project still exists in Docker).
|
|
65
|
+
*
|
|
66
|
+
* @param {string|number} devId - Developer ID
|
|
67
|
+
* @param {boolean} withVolumes - Pass `-v` to remove named volumes
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
async function tryComposeProjectDown(devId, withVolumes) {
|
|
71
|
+
const composeCmd = await dockerUtils.getComposeCommand();
|
|
72
|
+
const projectName = getInfraProjectName(devId);
|
|
73
|
+
const volFlag = withVolumes ? ' -v' : '';
|
|
74
|
+
await execAsyncWithCwd(`${composeCmd} -p "${projectName}" down${volFlag}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} name - Container name
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
async function dockerRmForceOne(name) {
|
|
82
|
+
try {
|
|
83
|
+
await execAsyncWithCwd(`docker rm -f "${name}"`);
|
|
84
|
+
logger.log(`Stopped and removed container: ${name}`);
|
|
85
|
+
} catch {
|
|
86
|
+
logger.log(`Container ${name} not running or already removed`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} vol - Volume name
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async function dockerVolumeRmForceOne(vol) {
|
|
95
|
+
try {
|
|
96
|
+
await execAsyncWithCwd(`docker volume rm -f "${vol}"`);
|
|
97
|
+
logger.log(`Removed volume: ${vol}`);
|
|
98
|
+
} catch {
|
|
99
|
+
logger.log(`Volume ${vol} not found or already removed`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Remove containers on the developer infra bridge network (covers strays).
|
|
105
|
+
* @param {string} networkName - Docker network name
|
|
106
|
+
* @returns {Promise<void>}
|
|
107
|
+
*/
|
|
108
|
+
async function removeContainersOnNetwork(networkName) {
|
|
109
|
+
let stdout = '';
|
|
110
|
+
try {
|
|
111
|
+
({ stdout } = await execAsyncWithCwd(
|
|
112
|
+
`docker network inspect "${networkName}" --format '{{json .Containers}}'`
|
|
113
|
+
));
|
|
114
|
+
} catch {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const raw = String(stdout || '').trim();
|
|
118
|
+
if (!raw || raw === 'null' || raw === '{}') return;
|
|
119
|
+
let containers;
|
|
120
|
+
try {
|
|
121
|
+
containers = JSON.parse(raw);
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const endpoint of Object.values(containers)) {
|
|
126
|
+
const name = endpoint && typeof endpoint.Name === 'string' ? endpoint.Name.replace(/^\//, '') : '';
|
|
127
|
+
if (name) await dockerRmForceOne(name);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove bridge network if present.
|
|
133
|
+
* @param {string} networkName - Docker network name
|
|
134
|
+
* @returns {Promise<void>}
|
|
135
|
+
*/
|
|
136
|
+
async function removeNetworkIfPresent(networkName) {
|
|
137
|
+
try {
|
|
138
|
+
await execAsyncWithCwd(`docker network rm "${networkName}"`);
|
|
139
|
+
logger.log(`Removed network: ${networkName}`);
|
|
140
|
+
} catch {
|
|
141
|
+
logger.log(`Network ${networkName} not removed (still in use or already gone)`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Last-resort teardown: remove known infra containers, optional volumes, then network.
|
|
147
|
+
*
|
|
148
|
+
* @param {string|number} devId - Developer ID
|
|
149
|
+
* @param {{ removeVolumes?: boolean }} [opts]
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
async function stopInfraDockerStackOrphaned(devId, opts = {}) {
|
|
153
|
+
const removeVolumes = opts.removeVolumes !== false;
|
|
154
|
+
const networkName = getInfraBridgeNetworkName(devId);
|
|
155
|
+
|
|
156
|
+
for (const name of getInfraServiceContainerNames(devId)) {
|
|
157
|
+
await dockerRmForceOne(name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await removeContainersOnNetwork(networkName);
|
|
161
|
+
|
|
162
|
+
if (removeVolumes) {
|
|
163
|
+
for (const vol of getInfraNamedVolumeCandidates(devId)) {
|
|
164
|
+
await dockerVolumeRmForceOne(vol);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await removeNetworkIfPresent(networkName);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
getInfraBridgeNetworkName,
|
|
173
|
+
getInfraServiceContainerNames,
|
|
174
|
+
getInfraNamedVolumeCandidates,
|
|
175
|
+
tryComposeProjectDown,
|
|
176
|
+
stopInfraDockerStackOrphaned
|
|
177
|
+
};
|
|
@@ -30,6 +30,23 @@ function extractKvKeysFromEnvContent(content) {
|
|
|
30
30
|
return [...keys];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* List builder app directories only (no integration/* apps).
|
|
35
|
+
* Used by `parameters validate` so sample integrations do not require catalog entries.
|
|
36
|
+
* @param {object} pathsUtil - paths module
|
|
37
|
+
* @returns {{ appKey: string, dir: string }[]}
|
|
38
|
+
*/
|
|
39
|
+
function listBuilderAppDirsForDiscovery(pathsUtil) {
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const name of pathsUtil.listBuilderAppNames()) {
|
|
42
|
+
const dir = pathsUtil.getBuilderPath(name);
|
|
43
|
+
if (fsRealSync.existsSync(dir)) {
|
|
44
|
+
out.push({ appKey: name, dir });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
/**
|
|
34
51
|
* List app directories for discovery (builder first, then integration-only apps).
|
|
35
52
|
* @param {object} pathsUtil - paths module
|
|
@@ -99,13 +116,20 @@ function discoverKvKeysFromEnvTemplatesForHook(pathsUtil, hook, catalog) {
|
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
119
|
+
* Keys `ensureInfraSecrets` (`up-infra`) may write locally:
|
|
120
|
+
* - {@link standardUpInfraEnsureKeys} in infra.parameter.yaml (explicit bootstrap + Redis/Postgres core)
|
|
121
|
+
* - `databases-{app}-{i}-*` derived from each workspace app `requires.databases`
|
|
122
|
+
* - `kv://` names from env.template lines whose catalog entry includes `upInfra`
|
|
123
|
+
*
|
|
124
|
+
* Does **not** union every `parameters[].ensureOn: upInfra` catalog entry—those would create dozens of
|
|
125
|
+
* unused Azure/BASH/app placeholders (often emptyString locally). Apps pull those via `resolve` / run when needed.
|
|
126
|
+
*
|
|
127
|
+
* @param {{ getStandardUpInfraBootstrapKeys: Function, keyMatchesEnsureHook: Function }} catalog
|
|
104
128
|
* @param {object} pathsUtil - paths module
|
|
105
129
|
* @returns {string[]}
|
|
106
130
|
*/
|
|
107
131
|
function getAllInfraEnsureKeys(catalog, pathsUtil) {
|
|
108
|
-
const set = new Set(
|
|
132
|
+
const set = new Set();
|
|
109
133
|
for (const k of catalog.getStandardUpInfraBootstrapKeys()) set.add(k);
|
|
110
134
|
for (const k of deriveDatabaseKvKeysFromWorkspace(pathsUtil)) set.add(k);
|
|
111
135
|
for (const k of discoverKvKeysFromEnvTemplatesForHook(pathsUtil, 'upInfra', catalog)) set.add(k);
|
|
@@ -117,5 +141,6 @@ module.exports = {
|
|
|
117
141
|
deriveDatabaseKvKeysFromWorkspace,
|
|
118
142
|
discoverKvKeysFromEnvTemplatesForHook,
|
|
119
143
|
getAllInfraEnsureKeys,
|
|
120
|
-
listAppDirsForDiscovery
|
|
144
|
+
listAppDirsForDiscovery,
|
|
145
|
+
listBuilderAppDirsForDiscovery
|
|
121
146
|
};
|
|
@@ -133,7 +133,10 @@ function createCatalogApi(doc, exact, patterns) {
|
|
|
133
133
|
|
|
134
134
|
function isKeyAllowedEmpty(key) {
|
|
135
135
|
const entry = findEntryForKey(key);
|
|
136
|
-
|
|
136
|
+
const gen = entry && entry.generator;
|
|
137
|
+
// emptyAllowed: e.g. Redis password when no requirepass.
|
|
138
|
+
// emptyString: optional user-supplied keys (OpenAI / Azure OpenAI) — absent resolves like ''.
|
|
139
|
+
return Boolean(gen && (gen.type === 'emptyAllowed' || gen.type === 'emptyString'));
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
function keyMatchesEnsureHook(key, hook) {
|
|
@@ -236,7 +239,7 @@ function readRelaxedUpInfraEnsureKeyList(catalogPath = DEFAULT_CATALOG_PATH) {
|
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
/**
|
|
239
|
-
* YAML-only: keys whose generator type is emptyAllowed (
|
|
242
|
+
* YAML-only: keys whose generator type is emptyAllowed or emptyString (optional / absent OK).
|
|
240
243
|
*
|
|
241
244
|
* @param {string} [catalogPath]
|
|
242
245
|
* @returns {Set<string>|null}
|
|
@@ -254,7 +257,7 @@ function readRelaxedEmptyAllowedKeySet(catalogPath = DEFAULT_CATALOG_PATH) {
|
|
|
254
257
|
typeof entry.key === 'string' &&
|
|
255
258
|
entry.key.trim() &&
|
|
256
259
|
entry.generator &&
|
|
257
|
-
entry.generator.type === 'emptyAllowed'
|
|
260
|
+
(entry.generator.type === 'emptyAllowed' || entry.generator.type === 'emptyString')
|
|
258
261
|
) {
|
|
259
262
|
set.add(entry.key.trim());
|
|
260
263
|
}
|
|
@@ -6,38 +6,86 @@
|
|
|
6
6
|
|
|
7
7
|
const { nodeFs } = require('../internal/node-fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
listBuilderAppDirsForDiscovery,
|
|
11
|
+
extractKvKeysFromEnvContent
|
|
12
|
+
} = require('./infra-kv-discovery');
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
function _scanEnvTemplate(fs, cwd, envPath) {
|
|
15
|
+
const rel = path.relative(cwd, envPath) || envPath;
|
|
16
|
+
try {
|
|
17
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
18
|
+
return { rel, keys: extractKvKeysFromEnvContent(content), readError: null };
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return { rel, keys: [], readError: e };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function _scanBuilderEnvTemplates(catalog, pathsUtil) {
|
|
19
25
|
const cwd = process.cwd();
|
|
20
26
|
const fs = nodeFs();
|
|
27
|
+
const errors = [];
|
|
28
|
+
const scannedApps = [];
|
|
29
|
+
const scannedEnvTemplates = [];
|
|
30
|
+
const kvKeysUnique = new Set();
|
|
21
31
|
|
|
22
|
-
for (const { dir } of
|
|
32
|
+
for (const { dir } of listBuilderAppDirsForDiscovery(pathsUtil)) {
|
|
33
|
+
scannedApps.push(path.relative(cwd, dir) || dir);
|
|
23
34
|
const envPath = path.join(dir, 'env.template');
|
|
24
35
|
if (!fs.existsSync(envPath)) continue;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
const scan = _scanEnvTemplate(fs, cwd, envPath);
|
|
37
|
+
scannedEnvTemplates.push(scan.rel);
|
|
38
|
+
if (scan.readError) {
|
|
39
|
+
errors.push({
|
|
40
|
+
key: '__read_error__',
|
|
41
|
+
envTemplatePath: scan.rel,
|
|
42
|
+
message: scan.readError.message
|
|
43
|
+
});
|
|
30
44
|
continue;
|
|
31
45
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
46
|
+
for (const k of scan.keys) {
|
|
47
|
+
kvKeysUnique.add(k);
|
|
34
48
|
if (!catalog.findEntryForKey(k)) {
|
|
35
|
-
errors.push(
|
|
49
|
+
errors.push({ key: k, envTemplatePath: scan.rel });
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
}
|
|
39
53
|
|
|
40
|
-
return {
|
|
54
|
+
return { errors, scannedApps, scannedEnvTemplates, kvKeysUnique };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validate that every kv:// key under builder app env.template files has catalog coverage.
|
|
59
|
+
* Integration apps under integration/ are not scanned (they often use ad-hoc kv keys).
|
|
60
|
+
* @param {{ findEntryForKey: Function }} catalog - Loaded catalog API
|
|
61
|
+
* @param {object} pathsUtil - paths module
|
|
62
|
+
* @returns {{
|
|
63
|
+
* valid: boolean,
|
|
64
|
+
* errors: { key: string, envTemplatePath: string, message?: string }[],
|
|
65
|
+
* summary: {
|
|
66
|
+
* scannedApps: string[],
|
|
67
|
+
* scannedEnvTemplates: string[],
|
|
68
|
+
* kvKeysUnique: string[],
|
|
69
|
+
* kvKeysCount: number
|
|
70
|
+
* }
|
|
71
|
+
* }}
|
|
72
|
+
*/
|
|
73
|
+
function validateWorkspaceKvRefsAgainstCatalog(catalog, pathsUtil) {
|
|
74
|
+
const { errors, scannedApps, scannedEnvTemplates, kvKeysUnique } = _scanBuilderEnvTemplates(
|
|
75
|
+
catalog,
|
|
76
|
+
pathsUtil
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
valid: errors.length === 0,
|
|
81
|
+
errors,
|
|
82
|
+
summary: {
|
|
83
|
+
scannedApps: scannedApps.sort(),
|
|
84
|
+
scannedEnvTemplates: scannedEnvTemplates.sort(),
|
|
85
|
+
kvKeysUnique: [...kvKeysUnique].sort(),
|
|
86
|
+
kvKeysCount: kvKeysUnique.size
|
|
87
|
+
}
|
|
88
|
+
};
|
|
41
89
|
}
|
|
42
90
|
|
|
43
91
|
/**
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared datasource resolver utilities.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally CLI-agnostic (no commander/options), so it can be reused by commands and tests.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Datasource path + JSON loading helpers
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const { resolveValidateInputPath, resolvePathFromIntegrationDatasourceKey } = require('../datasource/validate');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} fileOrKey
|
|
18
|
+
* @returns {string} Absolute path to datasource JSON file
|
|
19
|
+
*/
|
|
20
|
+
function resolveDatasourceJsonPath(fileOrKey) {
|
|
21
|
+
return resolveValidateInputPath(String(fileOrKey || '').trim());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Attempt to resolve a datasource key under integration/<app>/ without throwing.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} datasourceKey
|
|
28
|
+
* @returns {{ ok: true, path: string } | { ok: false, error: string }}
|
|
29
|
+
*/
|
|
30
|
+
function tryResolveDatasourceKeyToLocalPath(datasourceKey) {
|
|
31
|
+
try {
|
|
32
|
+
const p = resolvePathFromIntegrationDatasourceKey(String(datasourceKey || '').trim());
|
|
33
|
+
return { ok: true, path: p };
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return { ok: false, error: e?.message || String(e) };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} jsonPath
|
|
41
|
+
* @returns {any}
|
|
42
|
+
*/
|
|
43
|
+
function readJsonFile(jsonPath) {
|
|
44
|
+
const raw = fs.readFileSync(jsonPath, 'utf8');
|
|
45
|
+
return JSON.parse(raw);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
resolveDatasourceJsonPath,
|
|
50
|
+
tryResolveDatasourceKeyToLocalPath,
|
|
51
|
+
readJsonFile
|
|
52
|
+
};
|
|
53
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dimension file helpers for `aifabrix dimension create --file`.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Dimension file parsing
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} DimensionCreateInput
|
|
16
|
+
* @property {string} key
|
|
17
|
+
* @property {string} displayName
|
|
18
|
+
* @property {string} [description]
|
|
19
|
+
* @property {'string'|'number'|'boolean'} dataType
|
|
20
|
+
* @property {boolean} [isRequired]
|
|
21
|
+
* @property {Array<{ value: string, displayName?: string, description?: string }>} [values]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} filePath
|
|
26
|
+
* @returns {DimensionCreateInput}
|
|
27
|
+
*/
|
|
28
|
+
function readDimensionCreateFile(filePath) {
|
|
29
|
+
const p = path.resolve(String(filePath || '').trim());
|
|
30
|
+
if (!p) {
|
|
31
|
+
throw new Error('--file is required');
|
|
32
|
+
}
|
|
33
|
+
if (!fs.existsSync(p)) {
|
|
34
|
+
throw new Error(`File not found: ${p}`);
|
|
35
|
+
}
|
|
36
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(raw);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error(`Invalid JSON in ${p}: ${e.message}`);
|
|
42
|
+
}
|
|
43
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
44
|
+
throw new Error(`Dimension file must be a JSON object: ${p}`);
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
readDimensionCreateFile
|
|
51
|
+
};
|
|
52
|
+
|