@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
package/lib/app/run.js
CHANGED
|
@@ -10,15 +10,23 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
|
+
const {
|
|
14
|
+
formatWarningLine,
|
|
15
|
+
sectionTitle,
|
|
16
|
+
headerKeyValue,
|
|
17
|
+
metadata,
|
|
18
|
+
formatNextActions
|
|
19
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
13
20
|
const config = require('../core/config');
|
|
14
21
|
const logger = require('../utils/logger');
|
|
15
22
|
const pathsUtil = require('../utils/paths');
|
|
16
23
|
const { checkPortAvailable, waitForHealthCheck } = require('../utils/health-check');
|
|
17
24
|
const composeGenerator = require('../utils/compose-generator');
|
|
18
25
|
const containerHelpers = require('../utils/app-run-containers');
|
|
19
|
-
const mutagen = require('../utils/mutagen');
|
|
20
26
|
// Helper functions extracted to reduce file size and complexity
|
|
21
27
|
const helpers = require('./run-helpers');
|
|
28
|
+
const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
|
|
29
|
+
const { logRestartDevMountSummary } = require('./restart-display');
|
|
22
30
|
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
23
31
|
|
|
24
32
|
/**
|
|
@@ -73,8 +81,16 @@ async function validateAppForRun(appName, _debug) {
|
|
|
73
81
|
try {
|
|
74
82
|
const { isExternal, baseDir } = await detectAppType(appName);
|
|
75
83
|
if (baseDir !== 'builder' || isExternal) {
|
|
76
|
-
logger.log(
|
|
77
|
-
logger.log(
|
|
84
|
+
logger.log('');
|
|
85
|
+
logger.log(sectionTitle('Run'));
|
|
86
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
87
|
+
logger.log(
|
|
88
|
+
formatWarningLine('External integrations are not started as local Docker containers.')
|
|
89
|
+
);
|
|
90
|
+
logger.log(
|
|
91
|
+
metadata('Build and deploy the integration, then use its APIs from your environment.')
|
|
92
|
+
);
|
|
93
|
+
logger.log(formatNextActions([`aifabrix build ${appName}`]));
|
|
78
94
|
return false;
|
|
79
95
|
}
|
|
80
96
|
} catch (error) {
|
|
@@ -114,7 +130,7 @@ async function checkAndStopContainer(appName, appConfig, options, debug) {
|
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
|
|
117
|
-
logger.log(
|
|
133
|
+
logger.log(formatWarningLine(`Container ${containerName} is already running`));
|
|
118
134
|
await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
|
|
119
135
|
}
|
|
120
136
|
|
|
@@ -149,48 +165,6 @@ async function calculateHostPort(appConfig, options, debug) {
|
|
|
149
165
|
return hostPort;
|
|
150
166
|
}
|
|
151
167
|
|
|
152
|
-
/**
|
|
153
|
-
* When run --reload in dev with remote: ensure Mutagen sync session; return remote path for compose mount.
|
|
154
|
-
* Uses codePath (resolved build.context) as Mutagen local path so one config field drives both local and remote.
|
|
155
|
-
* When Docker endpoint, remote-server, or sync-ssh-host is localhost/127.0.0.1, returns null (no sync; use local path).
|
|
156
|
-
*
|
|
157
|
-
* @param {string} appName - Application name
|
|
158
|
-
* @param {string} developerId - Developer ID
|
|
159
|
-
* @param {boolean} debug - Debug flag
|
|
160
|
-
* @param {string} codePath - Resolved build.context (absolute path to app code)
|
|
161
|
-
* @param {string} [remoteSyncPath] - Optional relative path under user-mutagen-folder (from build.remoteSyncPath); when unset, defaults to dev/<appKey>
|
|
162
|
-
* @returns {Promise<string|null>} Remote path for -v mount or null if not remote/reload or already on server (localhost)
|
|
163
|
-
* @throws {Error} If --reload but remote not configured, or Mutagen install fails
|
|
164
|
-
*/
|
|
165
|
-
async function ensureReloadSync(appName, developerId, debug, codePath, remoteSyncPath) {
|
|
166
|
-
const endpoint = await config.getDockerEndpoint();
|
|
167
|
-
const serverUrl = await config.getRemoteServer();
|
|
168
|
-
if (!endpoint && !serverUrl) return null;
|
|
169
|
-
const syncSshHost = await config.getSyncSshHost();
|
|
170
|
-
if (isLocalhostEndpoint(endpoint) || isLocalhostEndpoint(serverUrl) || isLocalhostHost(syncSshHost || '')) {
|
|
171
|
-
if (debug) logger.log(chalk.gray('[DEBUG] Docker/remote/sync host is localhost; skipping Mutagen, using local path'));
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
const [userMutagenFolder, syncSshUser] = await Promise.all([
|
|
175
|
-
config.getUserMutagenFolder(),
|
|
176
|
-
config.getSyncSshUser()
|
|
177
|
-
]);
|
|
178
|
-
if (!userMutagenFolder || !syncSshUser || !syncSshHost) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
'run --reload requires remote server sync settings. Run "aifabrix dev init" or set user-mutagen-folder, sync-ssh-user, sync-ssh-host in config.'
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
const mutagenPath = await mutagen.ensureMutagenPath(logger.log);
|
|
184
|
-
const remotePath = mutagen.getRemotePath(userMutagenFolder, appName, remoteSyncPath);
|
|
185
|
-
const sshUrl = mutagen.getSyncSshUrl(syncSshUser, syncSshHost, remotePath);
|
|
186
|
-
const sessionName = mutagen.getSessionName(developerId, appName);
|
|
187
|
-
const localPath = (codePath && typeof codePath === 'string') ? codePath : pathsUtil.getBuilderPath(appName);
|
|
188
|
-
if (debug) logger.log(chalk.gray(`[DEBUG] Mutagen sync: ${sessionName} ${localPath} <-> ${sshUrl}`));
|
|
189
|
-
logger.log(chalk.blue('Ensuring Mutagen sync session...'));
|
|
190
|
-
await mutagen.ensureSyncSession(mutagenPath, sessionName, localPath, sshUrl);
|
|
191
|
-
return remotePath;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
168
|
/**
|
|
195
169
|
* Load and configure application
|
|
196
170
|
* @async
|
|
@@ -238,12 +212,21 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
|
|
|
238
212
|
devMountPath,
|
|
239
213
|
misoEnvironment
|
|
240
214
|
});
|
|
241
|
-
await helpers.displayRunStatus(
|
|
215
|
+
await helpers.displayRunStatus(
|
|
216
|
+
appName,
|
|
217
|
+
hostPort,
|
|
218
|
+
appConfig,
|
|
219
|
+
opts.runScopeOpts || null,
|
|
220
|
+
runOptions || {}
|
|
221
|
+
);
|
|
242
222
|
} catch (error) {
|
|
243
|
-
logger.log(
|
|
244
|
-
logger.log(
|
|
223
|
+
logger.log('');
|
|
224
|
+
logger.log(formatWarningLine(`Compose file preserved at ${tempComposePath}`));
|
|
225
|
+
logger.log(metadata(' Review the compose file, fix the issue, then run again.'));
|
|
245
226
|
if (runEnvPath || runEnvAdminPath) {
|
|
246
|
-
logger.log(
|
|
227
|
+
logger.log(
|
|
228
|
+
metadata(' Run .env file(s) contain secrets and were not deleted; remove manually if needed.')
|
|
229
|
+
);
|
|
247
230
|
}
|
|
248
231
|
if (debug) {
|
|
249
232
|
logger.log(chalk.gray(`[DEBUG] Error during container start: ${error.message}`));
|
|
@@ -271,30 +254,46 @@ async function resolveRunOptions(appName, appConfig, options, envKey, debug, eff
|
|
|
271
254
|
const codePath = pathsUtil.resolveBuildContext(builderPath, appConfig.build?.context || '.');
|
|
272
255
|
if (options.reload && envKey === 'dev') {
|
|
273
256
|
const remoteSyncPath = appConfig.build?.remoteSyncPath;
|
|
274
|
-
const
|
|
275
|
-
runOptions.
|
|
257
|
+
const reloadSummary = await ensureReloadSync(appName, appConfig.developerId, debug, codePath, remoteSyncPath);
|
|
258
|
+
runOptions.reloadSyncSummary = reloadSummary;
|
|
259
|
+
runOptions.devMountPath =
|
|
260
|
+
reloadSummary.transport === 'mutagen' ? reloadSummary.remotePath : codePath;
|
|
276
261
|
}
|
|
277
262
|
return runOptions;
|
|
278
263
|
}
|
|
279
264
|
|
|
280
265
|
/**
|
|
281
|
-
*
|
|
282
|
-
* @param {
|
|
283
|
-
* @param {Object}
|
|
284
|
-
* @param {boolean} debug - Debug flag
|
|
285
|
-
* @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
|
|
266
|
+
* When Traefik is enabled in user config, pass through for health checks (DNS + localhost order).
|
|
267
|
+
* @param {Object} runOptions
|
|
268
|
+
* @param {Object} userCfg
|
|
286
269
|
*/
|
|
287
|
-
|
|
288
|
-
|
|
270
|
+
function applyTraefikFlagToRunOptions(runOptions, userCfg) {
|
|
271
|
+
if (userCfg && userCfg.traefik === true) {
|
|
272
|
+
runOptions.traefikEnabled = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {string} envKey
|
|
278
|
+
* @throws {Error} If env is not dev, tst, or pro
|
|
279
|
+
*/
|
|
280
|
+
function assertValidRunEnvKey(envKey) {
|
|
289
281
|
if (envKey !== 'dev' && envKey !== 'tst' && envKey !== 'pro') {
|
|
290
282
|
throw new Error('--env must be dev, tst, or pro');
|
|
291
283
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Image resolution, prereqs, port, compose env — after app config and user config are loaded.
|
|
288
|
+
* @param {string} appName
|
|
289
|
+
* @param {Object} appConfig
|
|
290
|
+
* @param {Object} options - Original run options
|
|
291
|
+
* @param {string} envKey
|
|
292
|
+
* @param {Object} userCfg - getConfig() result
|
|
293
|
+
* @param {boolean} debug
|
|
294
|
+
* @returns {Promise<Object>} Prepared run context (same shape as prepareAppRun return)
|
|
295
|
+
*/
|
|
296
|
+
async function computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug) {
|
|
298
297
|
const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
|
|
299
298
|
const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
|
|
300
299
|
Boolean(userCfg.useEnvironmentScopedResources),
|
|
@@ -304,17 +303,24 @@ async function prepareAppRun(appName, options, debug) {
|
|
|
304
303
|
const runScopeOpts = effectiveEnvironmentScopedResources
|
|
305
304
|
? { effectiveEnvironmentScopedResources: true, env: envKey }
|
|
306
305
|
: null;
|
|
307
|
-
|
|
308
|
-
await
|
|
309
|
-
const
|
|
306
|
+
const { resolveRunImageWithLocalFallback } = require('./run-resolve-image');
|
|
307
|
+
const resolvedRef = await resolveRunImageWithLocalFallback(appName, appConfig, options);
|
|
308
|
+
const effectiveRunOptions = {
|
|
309
|
+
...options,
|
|
310
|
+
image: `${resolvedRef.imageName}:${resolvedRef.imageTag}`
|
|
311
|
+
};
|
|
312
|
+
await helpers.checkPrerequisites(appName, appConfig, debug, effectiveRunOptions.skipInfraCheck === true, effectiveRunOptions);
|
|
313
|
+
await checkAndStopContainer(appName, appConfig, effectiveRunOptions, debug);
|
|
314
|
+
const hostPort = await calculateHostPort(appConfig, effectiveRunOptions, debug);
|
|
310
315
|
const runOptions = await resolveRunOptions(
|
|
311
316
|
appName,
|
|
312
317
|
appConfig,
|
|
313
|
-
|
|
318
|
+
effectiveRunOptions,
|
|
314
319
|
envKey,
|
|
315
320
|
debug,
|
|
316
321
|
effectiveEnvironmentScopedResources
|
|
317
322
|
);
|
|
323
|
+
applyTraefikFlagToRunOptions(runOptions, userCfg);
|
|
318
324
|
const { composePath: tempComposePath, runEnvPath, runEnvAdminPath } = await helpers.prepareEnvironment(appName, appConfig, runOptions);
|
|
319
325
|
const result = {
|
|
320
326
|
appConfig,
|
|
@@ -329,6 +335,28 @@ async function prepareAppRun(appName, options, debug) {
|
|
|
329
335
|
return result;
|
|
330
336
|
}
|
|
331
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Prepare run: validate app, load config, check prereqs, stop existing container, resolve port and reload, prepare env.
|
|
340
|
+
* @param {string} appName - Application name
|
|
341
|
+
* @param {Object} options - Run options
|
|
342
|
+
* @param {boolean} debug - Debug flag
|
|
343
|
+
* @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
|
|
344
|
+
*/
|
|
345
|
+
async function prepareAppRun(appName, options, debug) {
|
|
346
|
+
const envKey = (options.env || 'dev').toLowerCase();
|
|
347
|
+
assertValidRunEnvKey(envKey);
|
|
348
|
+
const shouldContinue = await validateAppForRun(appName, debug);
|
|
349
|
+
if (!shouldContinue) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
logger.log('');
|
|
353
|
+
logger.log(sectionTitle('Run'));
|
|
354
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
355
|
+
const appConfig = await loadAndConfigureApp(appName, debug);
|
|
356
|
+
const userCfg = await config.getConfig();
|
|
357
|
+
return computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug);
|
|
358
|
+
}
|
|
359
|
+
|
|
332
360
|
/**
|
|
333
361
|
* Runs the application locally using Docker
|
|
334
362
|
* Starts container with proper port mapping and environment
|
|
@@ -360,10 +388,7 @@ async function runApp(appName, options = {}) {
|
|
|
360
388
|
if (debug) {
|
|
361
389
|
logger.log(chalk.gray(`[DEBUG] Compose file generated: ${prepared.tempComposePath}`));
|
|
362
390
|
}
|
|
363
|
-
|
|
364
|
-
logger.log(chalk.gray('With --reload: workspace mounted from host at /app (container runs as your user for write access).'));
|
|
365
|
-
logger.log(chalk.gray(` Host path: ${prepared.devMountPath}`));
|
|
366
|
-
}
|
|
391
|
+
logReloadDevSummary(Boolean(options.reload), prepared.mergedRunOptions.reloadSyncSummary);
|
|
367
392
|
await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
|
|
368
393
|
debug,
|
|
369
394
|
runEnvPath: prepared.runEnvPath,
|
|
@@ -401,6 +426,7 @@ async function restartApp(appName) {
|
|
|
401
426
|
const containerName = containerHelpers.getContainerName(appName, developerId);
|
|
402
427
|
try {
|
|
403
428
|
await execWithDockerEnv(`docker restart ${containerName}`);
|
|
429
|
+
await logRestartDevMountSummary(containerName);
|
|
404
430
|
} catch (error) {
|
|
405
431
|
const msg = (error.stderr || error.stdout || error.message || '').toLowerCase();
|
|
406
432
|
if (msg.includes('no such container') || msg.includes('is not running')) {
|
package/lib/build/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
formatSuccessLine,
|
|
3
|
+
formatSuccessParagraph,
|
|
4
|
+
formatProgress,
|
|
5
|
+
formatWarningLine,
|
|
6
|
+
sectionTitle,
|
|
7
|
+
headerKeyValue,
|
|
8
|
+
metadata,
|
|
9
|
+
formatNextActions
|
|
10
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
2
11
|
/**
|
|
3
12
|
* AI Fabrix Builder Build Functions
|
|
4
13
|
*
|
|
@@ -18,7 +27,6 @@ const paths = require('../utils/paths');
|
|
|
18
27
|
const { detectAppType, getProjectRoot } = require('../utils/paths');
|
|
19
28
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
20
29
|
const { loadConfigFile } = require('../utils/config-format');
|
|
21
|
-
const chalk = require('chalk');
|
|
22
30
|
const secrets = require('../core/secrets');
|
|
23
31
|
const config = require('../core/config');
|
|
24
32
|
const logger = require('../utils/logger');
|
|
@@ -28,6 +36,7 @@ const buildCopy = require('../utils/build-copy');
|
|
|
28
36
|
const { buildDevImageName } = require('../utils/image-name');
|
|
29
37
|
const buildHelpers = require('../utils/build-helpers');
|
|
30
38
|
const secretsEnvWrite = require('../core/secrets-env-write');
|
|
39
|
+
const { ensureRunSecretsForApp } = require('../app/run-env-compose');
|
|
31
40
|
|
|
32
41
|
/**
|
|
33
42
|
* Loads application config for an application
|
|
@@ -192,7 +201,7 @@ async function postBuildTasks(appName, buildConfig) {
|
|
|
192
201
|
// Note: processEnvVariables is already called by generateEnvFile to generate local .env
|
|
193
202
|
// at the envOutputPath, so we don't need to manually copy the docker .env file
|
|
194
203
|
} catch (error) {
|
|
195
|
-
logger.log(
|
|
204
|
+
logger.log(formatWarningLine(`Could not generate .env file: ${error.message}`));
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
|
|
@@ -205,8 +214,11 @@ async function postBuildTasks(appName, buildConfig) {
|
|
|
205
214
|
async function checkExternalAppType(appName) {
|
|
206
215
|
const variables = await loadVariablesYaml(appName);
|
|
207
216
|
if (variables.app && variables.app.type === 'external') {
|
|
208
|
-
logger.log(
|
|
209
|
-
logger.log(
|
|
217
|
+
logger.log('');
|
|
218
|
+
logger.log(sectionTitle('External integration'));
|
|
219
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
220
|
+
logger.log(metadata('No Docker image is built for external systems.'));
|
|
221
|
+
logger.log(formatNextActions([`aifabrix json ${appName}`]));
|
|
210
222
|
return true;
|
|
211
223
|
}
|
|
212
224
|
return false;
|
|
@@ -269,9 +281,9 @@ async function prepareDevDirectory(appName, buildConfig, options) {
|
|
|
269
281
|
const developerId = await config.getDeveloperId();
|
|
270
282
|
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
271
283
|
const directoryName = idNum === 0 ? 'applications' : `dev-${developerId}`;
|
|
272
|
-
logger.log(
|
|
284
|
+
logger.log(formatProgress(`Copying workspace (${directoryName})…`));
|
|
273
285
|
const devDir = await buildCopy.copyBuilderToDevDirectory(appName, developerId);
|
|
274
|
-
logger.log(formatSuccessLine(`
|
|
286
|
+
logger.log(formatSuccessLine(`Workspace ready: ${devDir}`));
|
|
275
287
|
|
|
276
288
|
const { config: appConfig, imageName } = await buildHelpers.loadAndValidateConfig(appName);
|
|
277
289
|
const effectiveImageName = buildDevImageName(imageName, developerId);
|
|
@@ -297,8 +309,11 @@ function prepareBuildContext(buildConfig, devDir) {
|
|
|
297
309
|
// Check if context is using old format (../appName) - these are incompatible with dev directory structure
|
|
298
310
|
if (buildConfig.context && buildConfig.context.startsWith('../') && buildConfig.context !== '../..') {
|
|
299
311
|
// Old format detected - always use devDir instead
|
|
300
|
-
logger.log(
|
|
301
|
-
|
|
312
|
+
logger.log(
|
|
313
|
+
formatWarningLine(
|
|
314
|
+
`Build context uses legacy path (${buildConfig.context}). Using dev workspace: ${devDir}`
|
|
315
|
+
)
|
|
316
|
+
);
|
|
302
317
|
contextPath = devDir;
|
|
303
318
|
} else if (buildConfig.context && buildConfig.context !== '../..') {
|
|
304
319
|
// Resolve relative context path from dev directory
|
|
@@ -370,6 +385,53 @@ async function handleDockerfileGeneration(appName, params, options, buildHelpers
|
|
|
370
385
|
}, generateDockerfile);
|
|
371
386
|
}
|
|
372
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Resolve Docker build-args from resolved env map and merged BASH_* secrets.
|
|
390
|
+
* @param {string} appName
|
|
391
|
+
* @returns {Promise<Object.<string, string>>}
|
|
392
|
+
*/
|
|
393
|
+
async function resolveDockerBuildArgsForApp(appName) {
|
|
394
|
+
const envMap = await secretsEnvWrite.resolveAndGetEnvMap(appName, { environment: 'docker' });
|
|
395
|
+
const { getBashPrefixedProcessEnvOverlay } = require('../utils/bash-secret-env');
|
|
396
|
+
const bashOverlay = await getBashPrefixedProcessEnvOverlay(null, appName);
|
|
397
|
+
const buildArgs = {};
|
|
398
|
+
if (envMap.NPM_TOKEN) buildArgs.NPM_TOKEN = envMap.NPM_TOKEN;
|
|
399
|
+
if (envMap.PYPI_TOKEN) buildArgs.PYPI_TOKEN = envMap.PYPI_TOKEN;
|
|
400
|
+
for (const [k, v] of Object.entries(bashOverlay)) {
|
|
401
|
+
if (v && buildArgs[k] === undefined) buildArgs[k] = v;
|
|
402
|
+
}
|
|
403
|
+
return buildArgs;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Standard Docker build path after CLI header is printed (non-external apps).
|
|
408
|
+
* @param {string} appName
|
|
409
|
+
* @param {Object} options
|
|
410
|
+
* @returns {Promise<string>} Built image ref e.g. name:tag
|
|
411
|
+
*/
|
|
412
|
+
async function runStandardDockerBuild(appName, options) {
|
|
413
|
+
const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
|
|
414
|
+
const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(appName, buildConfig, options);
|
|
415
|
+
const contextPath = prepareBuildContext(buildConfig, devDir);
|
|
416
|
+
const dockerfilePath = await handleDockerfileGeneration(appName, {
|
|
417
|
+
devDir,
|
|
418
|
+
buildConfig,
|
|
419
|
+
contextPath,
|
|
420
|
+
appConfig
|
|
421
|
+
}, options, buildHelpers);
|
|
422
|
+
await ensureRunSecretsForApp(appName);
|
|
423
|
+
const buildArgs = await resolveDockerBuildArgsForApp(appName);
|
|
424
|
+
const buildOptions = { ...options, buildArgs };
|
|
425
|
+
const tag = options.tag || 'latest';
|
|
426
|
+
if (typeof tag === 'string' && tag.includes(',')) {
|
|
427
|
+
throw new Error('Use a single image tag per build (comma-separated multiple tags are not supported).');
|
|
428
|
+
}
|
|
429
|
+
await dockerBuild.executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, buildOptions);
|
|
430
|
+
await postBuildTasks(appName, buildConfig);
|
|
431
|
+
logger.log(formatSuccessParagraph('Build completed successfully!'));
|
|
432
|
+
return `${effectiveImageName}:${tag}`;
|
|
433
|
+
}
|
|
434
|
+
|
|
373
435
|
/**
|
|
374
436
|
* Builds a container image for the specified application
|
|
375
437
|
* Auto-detects runtime and generates Dockerfile if needed
|
|
@@ -395,42 +457,10 @@ async function buildApp(appName, options = {}) {
|
|
|
395
457
|
}
|
|
396
458
|
|
|
397
459
|
try {
|
|
398
|
-
logger.log(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// 2. Prepare dev directory and copy files
|
|
404
|
-
const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(appName, buildConfig, options);
|
|
405
|
-
|
|
406
|
-
// 3. Prepare build context
|
|
407
|
-
const contextPath = prepareBuildContext(buildConfig, devDir);
|
|
408
|
-
|
|
409
|
-
// 4. Handle Dockerfile generation
|
|
410
|
-
const dockerfilePath = await handleDockerfileGeneration(appName, {
|
|
411
|
-
devDir,
|
|
412
|
-
buildConfig,
|
|
413
|
-
contextPath,
|
|
414
|
-
appConfig
|
|
415
|
-
}, options, buildHelpers);
|
|
416
|
-
|
|
417
|
-
// 5. Resolve NPM_TOKEN/PYPI_TOKEN for private registries and pass as build-args
|
|
418
|
-
const envMap = await secretsEnvWrite.resolveAndGetEnvMap(appName, { environment: 'docker' });
|
|
419
|
-
const buildArgs = {};
|
|
420
|
-
if (envMap.NPM_TOKEN) buildArgs.NPM_TOKEN = envMap.NPM_TOKEN;
|
|
421
|
-
if (envMap.PYPI_TOKEN) buildArgs.PYPI_TOKEN = envMap.PYPI_TOKEN;
|
|
422
|
-
const buildOptions = { ...options, buildArgs };
|
|
423
|
-
|
|
424
|
-
// 6. Execute Docker build
|
|
425
|
-
const tag = options.tag || 'latest';
|
|
426
|
-
await dockerBuild.executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, buildOptions);
|
|
427
|
-
|
|
428
|
-
// 7. Post-build tasks
|
|
429
|
-
await postBuildTasks(appName, buildConfig);
|
|
430
|
-
|
|
431
|
-
logger.log(formatSuccessParagraph('Build completed successfully!'));
|
|
432
|
-
return `${imageName}:${tag}`;
|
|
433
|
-
|
|
460
|
+
logger.log('');
|
|
461
|
+
logger.log(sectionTitle('Build'));
|
|
462
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
463
|
+
return await runStandardDockerBuild(appName, options);
|
|
434
464
|
} catch (error) {
|
|
435
465
|
throw new Error(`Build failed: ${error.message}`);
|
|
436
466
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `aifabrix doctor` action: environment validation + optional infra health.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const {
|
|
9
|
+
sectionTitle,
|
|
10
|
+
formatBulletSection,
|
|
11
|
+
formatDatasourceListRow,
|
|
12
|
+
formatWarningLine,
|
|
13
|
+
metadata
|
|
14
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
15
|
+
const validator = require('../validation/validator');
|
|
16
|
+
const config = require('../core/config');
|
|
17
|
+
const infra = require('../infrastructure');
|
|
18
|
+
const logger = require('../utils/logger');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} label
|
|
22
|
+
* @param {'ok'|'warning'|'fail'} variant
|
|
23
|
+
* @param {{ ok?: string, warn?: string, fail?: string }} text
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function formatDoctorEnvSummaryLine(label, variant, text) {
|
|
27
|
+
const base = chalk.gray(`${label}:`);
|
|
28
|
+
if (variant === 'ok') return `${base} ${chalk.green('✔')} ${chalk.white(text.ok || '')}`;
|
|
29
|
+
if (variant === 'warning') return `${base} ${chalk.yellow('⚠')} ${chalk.white(text.warn || '')}`;
|
|
30
|
+
return `${base} ${chalk.red('✖')} ${chalk.white(text.fail || '')}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} statusRaw
|
|
35
|
+
* @returns {'ok'|'warn'|'fail'}
|
|
36
|
+
*/
|
|
37
|
+
function doctorInfraRowAggregate(statusRaw) {
|
|
38
|
+
const s = String(statusRaw).trim().toLowerCase();
|
|
39
|
+
if (s === 'healthy') return 'ok';
|
|
40
|
+
if (s === 'unknown') return 'warn';
|
|
41
|
+
return 'fail';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {Object} result - `validator.checkEnvironment()` payload
|
|
46
|
+
*/
|
|
47
|
+
function logDoctorEnvironmentSection(result) {
|
|
48
|
+
logger.log('');
|
|
49
|
+
logger.log(sectionTitle('Environment check'));
|
|
50
|
+
logger.log('');
|
|
51
|
+
logger.log(
|
|
52
|
+
formatDoctorEnvSummaryLine(
|
|
53
|
+
'Docker',
|
|
54
|
+
result.docker === 'ok' ? 'ok' : 'fail',
|
|
55
|
+
{ ok: 'Running', fail: 'Not available' }
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
logger.log(
|
|
59
|
+
formatDoctorEnvSummaryLine(
|
|
60
|
+
'Ports',
|
|
61
|
+
result.ports === 'ok' ? 'ok' : 'warning',
|
|
62
|
+
{ ok: 'Available', warn: 'Some ports in use' }
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
logger.log(
|
|
66
|
+
formatDoctorEnvSummaryLine(
|
|
67
|
+
'Secrets',
|
|
68
|
+
result.secrets === 'ok' ? 'ok' : 'fail',
|
|
69
|
+
{ ok: 'Configured', fail: 'Missing' }
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
if (result.recommendations.length > 0) {
|
|
73
|
+
logger.log('');
|
|
74
|
+
logger.log(formatBulletSection('Recommendations:', result.recommendations));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {Object} result - `validator.checkEnvironment()` payload
|
|
80
|
+
*/
|
|
81
|
+
async function logDoctorInfraHealthSection(result) {
|
|
82
|
+
if (result.docker !== 'ok') {
|
|
83
|
+
logger.log('');
|
|
84
|
+
logger.log(metadata('Infrastructure health skipped (Docker not available).'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const cfg = await config.getConfig();
|
|
89
|
+
const health = await infra.checkInfraHealth(null, {
|
|
90
|
+
pgadmin: cfg.pgadmin !== false,
|
|
91
|
+
redisCommander: cfg.redisCommander !== false,
|
|
92
|
+
traefik: !!cfg.traefik
|
|
93
|
+
});
|
|
94
|
+
logger.log('');
|
|
95
|
+
logger.log(sectionTitle('Infrastructure health'));
|
|
96
|
+
Object.entries(health).forEach(([service, status]) => {
|
|
97
|
+
const agg = doctorInfraRowAggregate(status);
|
|
98
|
+
logger.log(formatDatasourceListRow(agg, `${service}: ${status}`, null));
|
|
99
|
+
});
|
|
100
|
+
} catch (_err) {
|
|
101
|
+
logger.log('');
|
|
102
|
+
logger.log(formatWarningLine('Infrastructure is not running or health could not be read.'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Runs the doctor checks and prints the TTY summary.
|
|
108
|
+
* @returns {Promise<void>}
|
|
109
|
+
*/
|
|
110
|
+
async function runDoctorCheck() {
|
|
111
|
+
const result = await validator.checkEnvironment();
|
|
112
|
+
logDoctorEnvironmentSection(result);
|
|
113
|
+
await logDoctorInfraHealthSection(result);
|
|
114
|
+
logger.log('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { runDoctorCheck };
|
package/lib/cli/index.js
CHANGED
|
@@ -21,8 +21,11 @@ const { setupParametersCommands } = require('./setup-parameters');
|
|
|
21
21
|
const { setupExternalSystemCommands } = require('./setup-external-system');
|
|
22
22
|
const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
|
|
23
23
|
const { setupDatasourceCommands } = require('../commands/datasource');
|
|
24
|
+
const { setupDimensionCommands } = require('../commands/dimension');
|
|
25
|
+
const { setupDimensionValueCommands } = require('../commands/dimension-value');
|
|
24
26
|
const { setupCredentialDeploymentCommands } = require('./setup-credential-deployment');
|
|
25
|
-
const {
|
|
27
|
+
const { setupIntegrationClientCommands } = require('./setup-integration-client');
|
|
28
|
+
const { setupPlatformCommands } = require('./setup-platform');
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Sets up all CLI commands on the Commander program instance
|
|
@@ -35,13 +38,16 @@ function setupCommands(program) {
|
|
|
35
38
|
setupEnvironmentCommands(program);
|
|
36
39
|
setupAppManagementCommands(program);
|
|
37
40
|
setupDatasourceCommands(program);
|
|
41
|
+
setupDimensionCommands(program);
|
|
42
|
+
setupDimensionValueCommands(program);
|
|
38
43
|
setupUtilityCommands(program);
|
|
39
44
|
setupCredentialDeploymentCommands(program);
|
|
40
|
-
|
|
45
|
+
setupIntegrationClientCommands(program);
|
|
41
46
|
setupExternalSystemCommands(program);
|
|
42
47
|
setupDevCommands(program);
|
|
43
48
|
setupSecretsCommands(program);
|
|
44
49
|
setupParametersCommands(program);
|
|
50
|
+
setupPlatformCommands(program);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
module.exports = {
|