@aifabrix/builder 2.44.5 → 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 +48 -2
- 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/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.js +20 -2
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +134 -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 +78 -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 +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- 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/up-common.js +79 -19
- 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 +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- 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/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 +1 -1
- 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 +71 -2
- 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 +23 -12
- 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 +1 -1
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- 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
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Guided (non-verbose) UX for infra/platform commands.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
const config = require('../core/config');
|
|
10
|
+
const devConfig = require('../utils/dev-config');
|
|
11
|
+
const pathsUtil = require('../utils/paths');
|
|
12
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
13
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
14
|
+
const { withMutedLogger } = require('../utils/with-muted-logger');
|
|
15
|
+
const { formatBlockingError, formatProgress, formatSuccessLine, successGlyph } = require('../utils/cli-test-layout-chalk');
|
|
16
|
+
const { computePublicUrlBaseString } = require('../utils/url-declarative-public-base');
|
|
17
|
+
const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('../utils/health-check-url');
|
|
18
|
+
const ora = require('ora');
|
|
19
|
+
const infra = require('../infrastructure');
|
|
20
|
+
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
21
|
+
const { getLogLevel, passesLevelFilter } = require('../commands/app-logs');
|
|
22
|
+
const { handleLogin } = require('../commands/login');
|
|
23
|
+
const healthCheck = require('../utils/health-check');
|
|
24
|
+
const { prepareUrlsLocalRegistryForUpPlatform } = require('../commands/up-common');
|
|
25
|
+
const { assertDevInfraUp } = require('../commands/dev-infra-gate');
|
|
26
|
+
|
|
27
|
+
const SEPARATOR = '────────────────────────────────────────';
|
|
28
|
+
|
|
29
|
+
function title(text) {
|
|
30
|
+
return chalk.bold(text);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shouldUseSpinner() {
|
|
34
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function startSpinner(text) {
|
|
38
|
+
if (!shouldUseSpinner()) {
|
|
39
|
+
logger.log(formatProgress(text));
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return ora({ text, spinner: 'dots' }).start();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stopSpinnerSuccess(spinner, text) {
|
|
46
|
+
if (!spinner) {
|
|
47
|
+
logger.log(formatSuccessLine(text));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
spinner.succeed(text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function getRunningContainerNameForApp(appName) {
|
|
54
|
+
const apps = await infra.getAppStatus();
|
|
55
|
+
const hit = (apps || []).find(a => a && a.name === appName && a.container);
|
|
56
|
+
return hit ? hit.container : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function hasErrorLogs(appName, opts = {}) {
|
|
60
|
+
const tailLines = Number.isFinite(opts.tailLines) ? opts.tailLines : 200;
|
|
61
|
+
const container = await getRunningContainerNameForApp(appName);
|
|
62
|
+
if (!container) return false;
|
|
63
|
+
const { stdout } = await execWithDockerEnv(`docker logs --tail ${tailLines} ${container}`);
|
|
64
|
+
const lines = String(stdout || '').split('\n');
|
|
65
|
+
return lines.some(line => passesLevelFilter(getLogLevel(line), 'error'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function validateAppErrorLogs(appNames) {
|
|
69
|
+
const names = Array.isArray(appNames) ? appNames : [];
|
|
70
|
+
const results = await Promise.all(
|
|
71
|
+
names.map(async(name) => {
|
|
72
|
+
try {
|
|
73
|
+
const hasErrors = await hasErrorLogs(name, { tailLines: 300 });
|
|
74
|
+
return { name, hasErrors };
|
|
75
|
+
} catch {
|
|
76
|
+
return { name, hasErrors: false };
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
const bad = results.filter(r => r && r.hasErrors);
|
|
81
|
+
if (bad.length === 0) return;
|
|
82
|
+
|
|
83
|
+
logger.log(chalk.yellow('⚠ Some services reported error logs'));
|
|
84
|
+
for (const r of bad) {
|
|
85
|
+
logger.log(chalk.gray(` Run: aifabrix logs ${r.name} -l error`));
|
|
86
|
+
}
|
|
87
|
+
logger.log('');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function waitForAppReady(appName, opts = {}) {
|
|
91
|
+
const timeoutSeconds = Number.isFinite(opts.timeoutSeconds) ? opts.timeoutSeconds : 90;
|
|
92
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
93
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
94
|
+
const appConfig = loadConfigFile(configPath) || {};
|
|
95
|
+
|
|
96
|
+
const apps = await infra.getAppStatus();
|
|
97
|
+
const hit = (apps || []).find(a => a && a.name === appName && typeof a.url === 'string');
|
|
98
|
+
const portMatch = hit && hit.url ? hit.url.match(/:(\d+)$/) : null;
|
|
99
|
+
const hostPort = portMatch ? parseInt(portMatch[1], 10) : null;
|
|
100
|
+
if (!hostPort) {
|
|
101
|
+
throw new Error(`Could not determine host port for ${appName}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const userCfg = await config.getConfig();
|
|
105
|
+
const healthRunOpts = { profile: 'docker' };
|
|
106
|
+
if (userCfg && userCfg.traefik === true) {
|
|
107
|
+
healthRunOpts.traefikEnabled = true;
|
|
108
|
+
}
|
|
109
|
+
await healthCheck.waitForHealthCheck(appName, timeoutSeconds, hostPort, appConfig, false, healthRunOpts);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveInfraHost(remoteServer) {
|
|
113
|
+
const raw = String(remoteServer || '').trim().replace(/\/+$/, '');
|
|
114
|
+
if (!raw) return 'localhost';
|
|
115
|
+
try {
|
|
116
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
117
|
+
const u = new URL(withScheme);
|
|
118
|
+
return u.hostname || 'localhost';
|
|
119
|
+
} catch {
|
|
120
|
+
// Fallback for non-URL strings.
|
|
121
|
+
return raw.replace(/^https?:\/\//i, '').split('/')[0].split(':')[0] || 'localhost';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function computeAppBaseUrl(appName) {
|
|
126
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
127
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
128
|
+
const variables = loadConfigFile(configPath) || {};
|
|
129
|
+
const basePort = Number(variables.port || 3000);
|
|
130
|
+
const developerIdRaw = await config.getDeveloperId();
|
|
131
|
+
const developerIdNum = typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
|
|
132
|
+
const userCfg = await config.getConfig();
|
|
133
|
+
const remoteServer = await config.getRemoteServer();
|
|
134
|
+
const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
|
|
135
|
+
|
|
136
|
+
const fd = variables.frontDoorRouting || null;
|
|
137
|
+
const frontDoorEnabled = Boolean(fd && fd.enabled === true);
|
|
138
|
+
const traefikOn = Boolean(userCfg && userCfg.traefik);
|
|
139
|
+
const pathActive = Boolean(traefikOn && frontDoorEnabled);
|
|
140
|
+
|
|
141
|
+
const publicBase = computePublicUrlBaseString({
|
|
142
|
+
traefik: traefikOn,
|
|
143
|
+
pathActive,
|
|
144
|
+
hostTemplate: fd ? fd.host : null,
|
|
145
|
+
tls: fd ? fd.tls : true,
|
|
146
|
+
developerIdRaw,
|
|
147
|
+
remoteServer,
|
|
148
|
+
// These guided bootstrap commands start services via Docker Compose, so we want the
|
|
149
|
+
// docker-published host port (no workstation +10 offset).
|
|
150
|
+
profile: 'docker',
|
|
151
|
+
listenPort: basePort,
|
|
152
|
+
developerIdNum,
|
|
153
|
+
infraTlsEnabled
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (pathActive) {
|
|
157
|
+
const mount = normalizeFrontDoorPatternForHealth(fd.pattern);
|
|
158
|
+
return joinUrlPath(publicBase, mount);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// No Traefik front-door routing: publicBase already includes correct scheme/host/port (localhost or remote-server).
|
|
162
|
+
return String(publicBase).replace(/\/+$/, '');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function logSection(label, value) {
|
|
166
|
+
logger.log(title(label));
|
|
167
|
+
logger.log(` ${value}`);
|
|
168
|
+
logger.log('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function logFooterStart(label) {
|
|
172
|
+
logger.log('');
|
|
173
|
+
logger.log(SEPARATOR);
|
|
174
|
+
logger.log(title(label));
|
|
175
|
+
logger.log(SEPARATOR);
|
|
176
|
+
logger.log('');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function logFooterEnd() {
|
|
180
|
+
logger.log(SEPARATOR);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function logPlatformReadyFooter() {
|
|
184
|
+
const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
|
|
185
|
+
resolveControllerUrl(),
|
|
186
|
+
computeAppBaseUrl('dataplane'),
|
|
187
|
+
computeAppBaseUrl('keycloak')
|
|
188
|
+
]);
|
|
189
|
+
const cfg = await config.getConfig();
|
|
190
|
+
const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
191
|
+
|
|
192
|
+
logFooterStart('Platform Ready');
|
|
193
|
+
logSection('Environment', env);
|
|
194
|
+
logSection('Miso Controller', controllerUrl);
|
|
195
|
+
logSection('Dataplane (DEV)', dataplaneUrl);
|
|
196
|
+
logSection('Keycloak', keycloakUrl);
|
|
197
|
+
logSection('API Documentation', `${dataplaneUrl}/api/docs`);
|
|
198
|
+
logSection('Knowledge Base', 'https://docs.aifabrix.ai');
|
|
199
|
+
logSection('Getting Started', 'https://docs.aifabrix.ai/get-started');
|
|
200
|
+
logFooterEnd();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function logMisoReadyFooter() {
|
|
204
|
+
const [controllerUrl, keycloakUrl] = await Promise.all([
|
|
205
|
+
resolveControllerUrl(),
|
|
206
|
+
computeAppBaseUrl('keycloak')
|
|
207
|
+
]);
|
|
208
|
+
logFooterStart('Miso Ready');
|
|
209
|
+
logSection('Miso Controller', controllerUrl);
|
|
210
|
+
logSection('Keycloak', keycloakUrl);
|
|
211
|
+
logFooterEnd();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function logDataplaneReadyFooter() {
|
|
215
|
+
const dataplaneUrl = await computeAppBaseUrl('dataplane');
|
|
216
|
+
const cfg = await config.getConfig();
|
|
217
|
+
const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
218
|
+
logFooterStart('Dataplane Ready');
|
|
219
|
+
logSection('Environment', env);
|
|
220
|
+
logSection('Dataplane (DEV)', dataplaneUrl);
|
|
221
|
+
logSection('API Documentation', `${dataplaneUrl}/api/docs`);
|
|
222
|
+
logFooterEnd();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function getInfraHostAndPorts() {
|
|
226
|
+
const developerId = await config.getDeveloperId();
|
|
227
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
228
|
+
const ports = devConfig.getDevPorts(idNum);
|
|
229
|
+
const remoteServer = await config.getRemoteServer();
|
|
230
|
+
const host = resolveInfraHost(remoteServer);
|
|
231
|
+
return { idNum, ports, host };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runGuidedUpInfra(options, runUpInfraCommand) {
|
|
235
|
+
logger.log('');
|
|
236
|
+
logger.log(title('AI Fabrix Infrastructure Setup'));
|
|
237
|
+
logger.log(SEPARATOR);
|
|
238
|
+
logger.log('');
|
|
239
|
+
const spin = startSpinner('Starting infrastructure services...');
|
|
240
|
+
await withMutedLogger(() => runUpInfraCommand(options), {
|
|
241
|
+
allow: (msg) => {
|
|
242
|
+
if (typeof msg !== 'string') return false;
|
|
243
|
+
return (
|
|
244
|
+
msg.includes('✔ Developer ID set to') ||
|
|
245
|
+
msg.includes('✔ Traefik enabled and saved to config') ||
|
|
246
|
+
msg.includes('✔ Traefik disabled and saved to config') ||
|
|
247
|
+
msg.includes('✔ pgAdmin enabled and saved to config') ||
|
|
248
|
+
msg.includes('✔ pgAdmin disabled and saved to config') ||
|
|
249
|
+
msg.includes('✔ Redis Commander enabled and saved to config') ||
|
|
250
|
+
msg.includes('✔ Redis Commander disabled and saved to config')
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
stopSpinnerSuccess(spin, 'Infrastructure ready');
|
|
255
|
+
|
|
256
|
+
const [{ idNum, ports, host }, cfg] = await Promise.all([
|
|
257
|
+
getInfraHostAndPorts(),
|
|
258
|
+
config.getConfig()
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
logFooterStart('Infra Ready');
|
|
262
|
+
logSection('Developer', `dev${String(idNum).padStart(2, '0')} (id: ${idNum})`);
|
|
263
|
+
logSection('Postgres', `${host}:${ports.postgres}`);
|
|
264
|
+
logSection('Redis', `${host}:${ports.redis}`);
|
|
265
|
+
if (cfg.pgadmin !== false) logSection('pgAdmin', `http://${host}:${ports.pgadmin}`);
|
|
266
|
+
if (cfg.redisCommander !== false) logSection('Redis Commander', `http://${host}:${ports.redisCommander}`);
|
|
267
|
+
if (cfg.traefik) logSection('Traefik', `http://${host}:${ports.traefikHttp}`);
|
|
268
|
+
if (cfg.tlsEnabled === true) logSection('TLS mode', 'on');
|
|
269
|
+
logFooterEnd();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function runGuidedUpMiso(options, handleUpMiso) {
|
|
273
|
+
logger.log('');
|
|
274
|
+
logger.log(title('AI Fabrix Platform Setup'));
|
|
275
|
+
logger.log(SEPARATOR);
|
|
276
|
+
logger.log('');
|
|
277
|
+
const kcSpin = startSpinner('Starting Keycloak...');
|
|
278
|
+
await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
|
|
279
|
+
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
280
|
+
logger.log('');
|
|
281
|
+
const mcSpin = startSpinner('Starting Miso Controller...');
|
|
282
|
+
await waitForAppReady('miso-controller', { timeoutSeconds: 120 });
|
|
283
|
+
stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
|
|
284
|
+
await logMisoReadyFooter();
|
|
285
|
+
await validateAppErrorLogs(['miso-controller']);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function runGuidedUpDataplane(options, handleUpDataplane) {
|
|
289
|
+
logger.log('');
|
|
290
|
+
logger.log(title('AI Fabrix Dataplane Setup'));
|
|
291
|
+
logger.log(SEPARATOR);
|
|
292
|
+
logger.log('');
|
|
293
|
+
// Infra must be checked before auth: device login talks to the controller, which needs
|
|
294
|
+
// Postgres/Redis when the controller runs locally — a down infra looks like "network error".
|
|
295
|
+
const infraSpin = startSpinner('Checking infrastructure...');
|
|
296
|
+
try {
|
|
297
|
+
await assertDevInfraUp({ quietSuccess: true });
|
|
298
|
+
stopSpinnerSuccess(infraSpin, 'Infrastructure is up');
|
|
299
|
+
} catch (infraErr) {
|
|
300
|
+
// Match up-miso: one error block from handleCommandError only (no spinner.fail / extra hints).
|
|
301
|
+
if (infraSpin) infraSpin.stop();
|
|
302
|
+
throw infraErr;
|
|
303
|
+
}
|
|
304
|
+
// Avoid an outer long-running spinner here: up-dataplane has multiple internal
|
|
305
|
+
// phases and may print some output. An outer spinner causes "spinner over spinner"
|
|
306
|
+
// rendering artifacts. Instead, do the same single auth step as up-platform, then
|
|
307
|
+
// show one spinner for the dataplane install/run phase.
|
|
308
|
+
await runGuidedAuthStep(handleLogin);
|
|
309
|
+
|
|
310
|
+
logger.log('');
|
|
311
|
+
const dpSpin = startSpinner('Starting Dataplane...');
|
|
312
|
+
await withMutedLogger(() =>
|
|
313
|
+
handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
|
|
314
|
+
);
|
|
315
|
+
stopSpinnerSuccess(dpSpin, 'Dataplane ready');
|
|
316
|
+
await validateAppErrorLogs(['dataplane']);
|
|
317
|
+
await logDataplaneReadyFooter();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Guided-mode summary after `up-platform --force` (printed under the platform header).
|
|
322
|
+
* @param {{ deviceCleared: number, clientCleared: number, environment: string, defaultControllerUrl: string }} forceSummary
|
|
323
|
+
* @param {string[]} cleanedApps
|
|
324
|
+
*/
|
|
325
|
+
function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
|
|
326
|
+
const check = successGlyph();
|
|
327
|
+
logger.log(chalk.bold('Clean installation files'));
|
|
328
|
+
logger.log(
|
|
329
|
+
chalk.gray(
|
|
330
|
+
` ${check} Cleared ${forceSummary.deviceCleared} device token(s) and ${forceSummary.clientCleared} client token(s)`
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
logger.log(chalk.gray(` ${check} Environment set to ${forceSummary.environment}`));
|
|
334
|
+
logger.log(
|
|
335
|
+
chalk.gray(
|
|
336
|
+
` ${check} Default controller set to ${forceSummary.defaultControllerUrl} (run aifabrix login after platform is up)`
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
for (const appName of cleanedApps || []) {
|
|
340
|
+
logger.log(chalk.gray(` ${check} Cleaned builder/${appName}`));
|
|
341
|
+
}
|
|
342
|
+
logger.log('');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function logGuidedPlatformSetupHeader() {
|
|
346
|
+
logger.log('');
|
|
347
|
+
logger.log(title('AI Fabrix Platform Setup'));
|
|
348
|
+
logger.log(SEPARATOR);
|
|
349
|
+
logger.log('');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function runGuidedAuthStep(handleLogin) {
|
|
353
|
+
const spin = startSpinner('Authenticating...');
|
|
354
|
+
try {
|
|
355
|
+
const controllerUrl = await resolveControllerUrl();
|
|
356
|
+
await handleLogin({ method: 'device', controller: controllerUrl, environment: 'dev', compact: true });
|
|
357
|
+
} catch (authErr) {
|
|
358
|
+
if (spin) spin.fail('Authentication failed');
|
|
359
|
+
logger.error(formatBlockingError('Authentication failed'));
|
|
360
|
+
logger.log(chalk.gray('\nRun:\n af login\n\nThen retry:\n af up-platform\n'));
|
|
361
|
+
throw authErr;
|
|
362
|
+
}
|
|
363
|
+
// Compact device-login already prints a success line; don't duplicate it.
|
|
364
|
+
if (spin) spin.stop();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, handleLogin, forceCleanSummary = null) {
|
|
368
|
+
logGuidedPlatformSetupHeader();
|
|
369
|
+
if (forceCleanSummary && forceCleanSummary.forceSummary) {
|
|
370
|
+
logUpPlatformForceCleanSummary(forceCleanSummary.forceSummary, forceCleanSummary.cleanedApps);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
await withMutedLogger(() => prepareUrlsLocalRegistryForUpPlatform());
|
|
374
|
+
|
|
375
|
+
const kcSpin = startSpinner('Starting Keycloak...');
|
|
376
|
+
await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
|
|
377
|
+
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
378
|
+
logger.log('');
|
|
379
|
+
|
|
380
|
+
const mcSpin = startSpinner('Starting Miso Controller...');
|
|
381
|
+
await waitForAppReady('miso-controller', { timeoutSeconds: 120 });
|
|
382
|
+
stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
|
|
383
|
+
logger.log('');
|
|
384
|
+
|
|
385
|
+
await runGuidedAuthStep(handleLogin);
|
|
386
|
+
|
|
387
|
+
logger.log('');
|
|
388
|
+
const dpSpin = startSpinner('Starting Dataplane...');
|
|
389
|
+
await withMutedLogger(() =>
|
|
390
|
+
handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
|
|
391
|
+
);
|
|
392
|
+
stopSpinnerSuccess(dpSpin, 'Dataplane ready');
|
|
393
|
+
await validateAppErrorLogs(['miso-controller', 'dataplane']);
|
|
394
|
+
await logPlatformReadyFooter();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function logStoppedServices(cfg) {
|
|
398
|
+
logger.log(title('Stopped'));
|
|
399
|
+
logger.log(' postgres');
|
|
400
|
+
logger.log(' redis');
|
|
401
|
+
if (cfg.pgadmin !== false) logger.log(' pgadmin');
|
|
402
|
+
if (cfg.redisCommander !== false) logger.log(' redis-commander');
|
|
403
|
+
if (cfg.traefik) logger.log(' traefik');
|
|
404
|
+
logger.log('');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function runGuidedDownInfra(appName, options, infra, appLib) {
|
|
408
|
+
logger.log('');
|
|
409
|
+
logger.log(title('AI Fabrix Shutdown'));
|
|
410
|
+
logger.log(SEPARATOR);
|
|
411
|
+
logger.log('');
|
|
412
|
+
|
|
413
|
+
const spin = startSpinner('Stopping infrastructure...');
|
|
414
|
+
await withMutedLogger(async() => {
|
|
415
|
+
if (typeof appName === 'string' && appName.trim().length > 0) {
|
|
416
|
+
await appLib.downApp(appName, { volumes: !!options.volumes });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (options.volumes) await infra.stopInfraWithVolumes();
|
|
420
|
+
else await infra.stopInfra();
|
|
421
|
+
});
|
|
422
|
+
stopSpinnerSuccess(spin, 'Infrastructure stopped');
|
|
423
|
+
|
|
424
|
+
const developerId = await config.getDeveloperId();
|
|
425
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
426
|
+
const cfg = await config.getConfig();
|
|
427
|
+
|
|
428
|
+
logFooterStart('Stopped');
|
|
429
|
+
logSection('Developer', `dev${String(idNum).padStart(2, '0')} (id: ${idNum})`);
|
|
430
|
+
logStoppedServices(cfg);
|
|
431
|
+
if (options.volumes) {
|
|
432
|
+
logger.log(chalk.yellow('⚠ Volumes removed: all local data deleted'));
|
|
433
|
+
logger.log('');
|
|
434
|
+
}
|
|
435
|
+
logFooterEnd();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
module.exports = {
|
|
439
|
+
runGuidedUpInfra,
|
|
440
|
+
runGuidedUpMiso,
|
|
441
|
+
runGuidedUpDataplane,
|
|
442
|
+
runGuidedUpPlatform,
|
|
443
|
+
runGuidedDownInfra
|
|
444
|
+
};
|
|
445
|
+
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -31,6 +31,19 @@ Example:
|
|
|
31
31
|
$ aifabrix push myapp -t v1.0.0
|
|
32
32
|
`;
|
|
33
33
|
|
|
34
|
+
const LOGS_HELP_AFTER = `
|
|
35
|
+
Examples:
|
|
36
|
+
$ aifabrix logs miso-controller -l error
|
|
37
|
+
$ aifabrix logs miso-controller -f
|
|
38
|
+
$ aifabrix logs dataplane -l warn
|
|
39
|
+
$ aifabrix logs dataplane -f -t 100
|
|
40
|
+
$ aifabrix logs keycloak -f -t 0
|
|
41
|
+
|
|
42
|
+
Notes:
|
|
43
|
+
- Platform apps use names such as miso-controller, dataplane, keycloak (see aifabrix run).
|
|
44
|
+
- Use -t 0 (--tail 0) for the full buffered log; default is 100 lines.
|
|
45
|
+
`;
|
|
46
|
+
|
|
34
47
|
/**
|
|
35
48
|
* Normalize options for external system creation
|
|
36
49
|
* @param {Object} options - Raw CLI options
|
|
@@ -188,11 +201,12 @@ See integration/hubspot-test/wizard-hubspot-e2e.yaml for an example.`;
|
|
|
188
201
|
|
|
189
202
|
function registerRunCommand(program) {
|
|
190
203
|
const runHelp = `
|
|
191
|
-
In dev: use --reload for
|
|
204
|
+
In dev: use --reload for live code in the container. Before start, the CLI prints a "Reload (dev)" section: direct bind mount when the Docker engine is on this machine, or Mutagen when the engine is remote.
|
|
192
205
|
Examples:
|
|
193
206
|
$ aifabrix run myapp
|
|
194
207
|
$ aifabrix run myapp --env tst
|
|
195
208
|
$ aifabrix run myapp --tag v1.0.0
|
|
209
|
+
$ aifabrix run myapp --base
|
|
196
210
|
$ aifabrix run myapp --reload`;
|
|
197
211
|
program.command('run <app>')
|
|
198
212
|
.description('Run app locally or on remote Docker host')
|
|
@@ -200,7 +214,8 @@ Examples:
|
|
|
200
214
|
.option('-d, --debug', 'Enable debug output with detailed container information')
|
|
201
215
|
.option('-t, --tag <tag>', 'Image tag to run (e.g. v1.0.0); overrides application.yaml image.tag')
|
|
202
216
|
.option('-e, --env <env>', 'Environment: dev (default), tst, or pro', 'dev')
|
|
203
|
-
.option('--
|
|
217
|
+
.option('--base', 'Use manifest base image only (skip local developer-scoped tag preference)')
|
|
218
|
+
.option('--reload', 'In dev: mount workspace into container (Mutagen only if docker-endpoint is a remote host)')
|
|
204
219
|
.addHelpText('after', runHelp)
|
|
205
220
|
.action(async(appName, options) => {
|
|
206
221
|
try {
|
|
@@ -219,6 +234,7 @@ function setupBuildRunLogsDownCommands(program) {
|
|
|
219
234
|
.option('-f, --force-template', 'Force rebuild from template')
|
|
220
235
|
.option('--no-cache', 'Full Docker rebuild (disable layer cache); use after Dockerfile or context fixes')
|
|
221
236
|
.option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in application.yaml to match for deploy.')
|
|
237
|
+
.option('--base', 'Also tag the manifest base image name (after developer-scoped build when developer id > 0)')
|
|
222
238
|
.action(async(appName, options) => {
|
|
223
239
|
try {
|
|
224
240
|
const imageTag = await app.buildApp(appName, options);
|
|
@@ -236,6 +252,7 @@ function setupBuildRunLogsDownCommands(program) {
|
|
|
236
252
|
.option('-f', 'Follow log stream')
|
|
237
253
|
.option('-t, --tail <lines>', 'Number of lines (default: 100); 0 = full list', '100')
|
|
238
254
|
.option('-l, --level <level>', 'Show only logs at this level or above (debug|info|warn|error)')
|
|
255
|
+
.addHelpText('after', LOGS_HELP_AFTER)
|
|
239
256
|
.action(async(appName, options) => {
|
|
240
257
|
try {
|
|
241
258
|
const { runAppLogs } = require('../commands/app-logs');
|
|
@@ -328,6 +345,7 @@ function setupPushDeployDockerfileCommands(program) {
|
|
|
328
345
|
.option('--local', 'Send manifest to controller then run app locally (app: same as aifabrix run <app>; external: restart dataplane)')
|
|
329
346
|
.option('--client-id <id>', 'Client ID (overrides config)')
|
|
330
347
|
.option('--client-secret <secret>', 'Client Secret (overrides config)')
|
|
348
|
+
.option('--repository-url <url>', 'Repository URL for pipeline validation (default: application.yaml repository.repositoryUrl, else https://github.com/aifabrix/<appKey>)')
|
|
331
349
|
.option('--poll', 'Poll for deployment status', true)
|
|
332
350
|
.option('--no-poll', 'Do not poll for status')
|
|
333
351
|
.option('--probe', 'After external deploy, run dataplane runtime checks (validation/run); slower')
|
package/lib/cli/setup-auth.js
CHANGED
|
@@ -23,6 +23,9 @@ Examples:
|
|
|
23
23
|
const AUTH_HELP_AFTER = `
|
|
24
24
|
Without options: show auth status (same as: aifabrix auth status).
|
|
25
25
|
With --set-controller or --set-environment: write defaults to config.yaml.
|
|
26
|
+
Aliases:
|
|
27
|
+
aifabrix auth set-controller <url>
|
|
28
|
+
aifabrix auth set-environment <env>
|
|
26
29
|
Subcommand: auth status [--validate] for CI/scripts.
|
|
27
30
|
`;
|
|
28
31
|
|
|
@@ -95,6 +98,29 @@ function setupAuthSubcommands(program) {
|
|
|
95
98
|
process.exit(1);
|
|
96
99
|
}
|
|
97
100
|
});
|
|
101
|
+
|
|
102
|
+
auth.command('set-controller <url>')
|
|
103
|
+
.description('Set default controller URL in config (alias for auth --set-controller)')
|
|
104
|
+
.action(async(url) => {
|
|
105
|
+
try {
|
|
106
|
+
await handleAuthConfig({ setController: url });
|
|
107
|
+
} catch (error) {
|
|
108
|
+
handleCommandError(error, 'auth set-controller');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
auth.command('set-environment <env>')
|
|
114
|
+
.description('Set default environment in config (alias for auth --set-environment)')
|
|
115
|
+
.action(async(env) => {
|
|
116
|
+
try {
|
|
117
|
+
await handleAuthConfig({ setEnvironment: env });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
handleCommandError(error, 'auth set-environment');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
98
124
|
auth.command('status')
|
|
99
125
|
.description('Show tokens/session for current controller and environment')
|
|
100
126
|
.option('--validate', 'Exit with code 1 when not authenticated (for scripted use, e.g. manual test setup)')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dev subcommands: set-home, set-work, print-home, print-work, set-format.
|
|
2
|
+
* Dev subcommands: set-home, set-work, print-home, print-work, shell-env, set-format.
|
|
3
3
|
*
|
|
4
4
|
* @fileoverview Path and format CLI registration for `aifabrix dev`
|
|
5
5
|
* @author AI Fabrix Team
|
|
@@ -10,18 +10,35 @@
|
|
|
10
10
|
const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
|
+
const path = require('path');
|
|
13
14
|
const config = require('../core/config');
|
|
14
15
|
const logger = require('../utils/logger');
|
|
15
16
|
const paths = require('../utils/paths');
|
|
16
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
registerAifabrixShellEnvFromConfig,
|
|
19
|
+
buildShellEnvExportsFromConfig
|
|
20
|
+
} = require('../utils/register-aifabrix-shell-env');
|
|
17
21
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
18
22
|
|
|
19
23
|
async function runShellEnvRegistration() {
|
|
20
24
|
try {
|
|
21
25
|
await registerAifabrixShellEnvFromConfig(config.getConfig);
|
|
22
26
|
logger.log(
|
|
23
|
-
chalk.gray(' User/shell environment updated.
|
|
27
|
+
chalk.gray(' User/shell environment updated. New terminals pick up AIFABRIX_HOME / AIFABRIX_WORK.')
|
|
24
28
|
);
|
|
29
|
+
if (process.platform !== 'win32') {
|
|
30
|
+
logger.log(
|
|
31
|
+
chalk.gray(' This terminal: ') +
|
|
32
|
+
chalk.cyan('eval "$(aifabrix dev shell-env)"') +
|
|
33
|
+
chalk.gray(' (or source the aifabrix-shell-env.sh next to config.yaml).')
|
|
34
|
+
);
|
|
35
|
+
} else {
|
|
36
|
+
logger.log(
|
|
37
|
+
chalk.gray(' This PowerShell session: ') +
|
|
38
|
+
chalk.cyan('aifabrix dev shell-env') +
|
|
39
|
+
chalk.gray(' then run the printed lines.')
|
|
40
|
+
);
|
|
41
|
+
}
|
|
25
42
|
} catch (regErr) {
|
|
26
43
|
throw new Error(`Config saved but environment registration failed: ${regErr.message}`);
|
|
27
44
|
}
|
|
@@ -92,6 +109,36 @@ function addPrintHomeWorkCommands(dev) {
|
|
|
92
109
|
process.exit(1);
|
|
93
110
|
}
|
|
94
111
|
});
|
|
112
|
+
|
|
113
|
+
dev
|
|
114
|
+
.command('shell-env')
|
|
115
|
+
.description(
|
|
116
|
+
'Print export lines for AIFABRIX_HOME / AIFABRIX_WORK (stdout only). POSIX: eval "$(aifabrix dev shell-env)"'
|
|
117
|
+
)
|
|
118
|
+
.action(async() => {
|
|
119
|
+
try {
|
|
120
|
+
if (process.platform === 'win32') {
|
|
121
|
+
const cfg = await config.getConfig();
|
|
122
|
+
const home = (cfg['aifabrix-home'] && String(cfg['aifabrix-home']).trim())
|
|
123
|
+
? paths.resolveAifabrixHomeLikePath(String(cfg['aifabrix-home']).trim())
|
|
124
|
+
: null;
|
|
125
|
+
const work = (cfg['aifabrix-work'] && String(cfg['aifabrix-work']).trim())
|
|
126
|
+
? path.resolve(String(cfg['aifabrix-work']).trim())
|
|
127
|
+
: null;
|
|
128
|
+
const esc = (s) => String(s).replace(/'/g, '\'\'');
|
|
129
|
+
const lines = ['# Paste into PowerShell for this session only.'];
|
|
130
|
+
if (home) lines.push(`$env:AIFABRIX_HOME = '${esc(home)}'`);
|
|
131
|
+
if (work) lines.push(`$env:AIFABRIX_WORK = '${esc(work)}'`);
|
|
132
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const body = await buildShellEnvExportsFromConfig(config.getConfig);
|
|
136
|
+
process.stdout.write(body);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
handleCommandError(error, 'dev shell-env');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
function addSetFormatCommand(dev, handleSetFormat) {
|