@aifabrix/builder 2.44.5 → 2.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cli-layout.mdc +8 -4
- package/.cursor/rules/project-rules.mdc +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 +104 -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/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +126 -0
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +58 -19
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +148 -74
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +83 -49
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +460 -0
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +31 -3
- package/lib/cli/setup-auth.js +98 -27
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +132 -118
- 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-resolve.js +132 -0
- package/lib/cli/setup-utility.js +143 -84
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- 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 +468 -0
- package/lib/commands/setup-prompts.js +421 -0
- package/lib/commands/setup.js +254 -0
- package/lib/commands/teardown.js +277 -0
- package/lib/commands/up-common.js +113 -19
- package/lib/commands/up-dataplane.js +44 -19
- package/lib/commands/up-miso.js +18 -18
- package/lib/commands/upload.js +111 -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/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- 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 +428 -0
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +29 -1
- package/lib/core/secrets-load.js +252 -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 +9 -2
- 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/internal/node-fs.js +2 -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/application-schema.json +4 -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 -1
- 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-config-resolver.js +24 -1
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- 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/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- 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/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- 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 +28 -10
- package/lib/utils/health-check.js +139 -107
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +308 -76
- 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 +49 -4
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-canonical.js +10 -3
- 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 +26 -13
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +42 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -388
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +52 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +158 -76
- 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 +3 -1
- package/templates/applications/dataplane/application.yaml +5 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +9 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.npmrc.token +0 -1
- 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
|
@@ -15,6 +15,99 @@ const paths = require('./paths');
|
|
|
15
15
|
const { getInfraDirName } = require('../infrastructure/helpers');
|
|
16
16
|
const { resolveMisoEnvironment } = require('./compose-miso-env');
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* When running a pulled Python image (no bind-mounted source), the image CMD may invoke the
|
|
20
|
+
* `uvicorn` console script, which is not always on PATH. Derive a `python -m uvicorn` command from
|
|
21
|
+
* `build.reloadStart` so generated compose matches the same app entrypoint as hot-reload runs.
|
|
22
|
+
*
|
|
23
|
+
* @param {string|undefined} reloadStart - Value from application.yaml `build.reloadStart`
|
|
24
|
+
* @returns {string|null} Shell command fragment (no `cd`, no `exec`; image compose prepends `exec ` in JS)
|
|
25
|
+
*/
|
|
26
|
+
function derivePythonImageStartFromReload(reloadStart) {
|
|
27
|
+
if (typeof reloadStart !== 'string' || !reloadStart.trim()) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const trimmed = reloadStart.trim().replace(/\s+--reload\b/g, '').trim();
|
|
31
|
+
if (/^python3?\s+-m\s+uvicorn\s+/i.test(trimmed)) {
|
|
32
|
+
return normalizeComposeShellPortRef(trimmed);
|
|
33
|
+
}
|
|
34
|
+
if (trimmed.startsWith('uvicorn ')) {
|
|
35
|
+
const rest = trimmed.slice('uvicorn '.length);
|
|
36
|
+
return normalizeComposeShellPortRef(`python -m uvicorn ${rest}`);
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compose sets `PORT` in the service environment; replace `${PORT:-...}` with `$$PORT` in the
|
|
43
|
+
* fragment so the compose file avoids ambiguous `:` parsing in flow scalars and Compose expands
|
|
44
|
+
* `$$` to `$` for the container shell (which then expands `PORT` from the service environment).
|
|
45
|
+
*
|
|
46
|
+
* @param {string} cmd - Shell command fragment
|
|
47
|
+
* @returns {string} Same fragment with `${PORT:-...}` replaced by `$$PORT` (Compose escapes `$$` → `$` for the shell)
|
|
48
|
+
*/
|
|
49
|
+
function normalizeComposeShellPortRef(cmd) {
|
|
50
|
+
return cmd.replace(/\$\{PORT:-[^}]+\}/g, () => '$$PORT');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Normalizes `build.reloadStart` for Python when using a bind-mounted source in compose.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} raw - Trimmed reload command
|
|
57
|
+
* @returns {string} Command suitable for `cd /app && …` in compose
|
|
58
|
+
*/
|
|
59
|
+
function normalizePythonReloadForComposeMounted(raw) {
|
|
60
|
+
const s = normalizeComposeShellPortRef(raw);
|
|
61
|
+
if (/^python3?\s+-m\s+uvicorn\s+/i.test(s)) {
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
64
|
+
if (s.startsWith('uvicorn ')) {
|
|
65
|
+
return `python -m uvicorn ${s.slice('uvicorn '.length)}`;
|
|
66
|
+
}
|
|
67
|
+
return s;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Builds the single `reloadStartCommand` passed to compose templates (`command: …` when set).
|
|
72
|
+
*
|
|
73
|
+
* `applications.<app>.reload` in config.yaml (and `aifabrix run --reload`) only controls **bind-mount +
|
|
74
|
+
* `--reload`** via `devMountPath`. For pulled images without a mount, use optional `build.imageRun` in
|
|
75
|
+
* application.yaml, or (Python only) derive from `build.reloadStart` when `imageRun` is unset.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} language - `application.yaml` build.language
|
|
78
|
+
* @param {string|null} devMountPath - Bind mount path when reload sync is active
|
|
79
|
+
* @param {string|undefined} reloadRaw - `build.reloadStart`
|
|
80
|
+
* @param {Object} [build] - `application.yaml` `build` object (`imageRun`, etc.)
|
|
81
|
+
* @returns {string|null}
|
|
82
|
+
*/
|
|
83
|
+
function buildReloadStartCommandForCompose(language, devMountPath, reloadRaw, build = {}) {
|
|
84
|
+
const buildObj = build && typeof build === 'object' ? build : {};
|
|
85
|
+
const imageRunRaw = typeof buildObj.imageRun === 'string' ? buildObj.imageRun.trim() : '';
|
|
86
|
+
const reloadTrimmed = typeof reloadRaw === 'string' ? reloadRaw.trim() : '';
|
|
87
|
+
|
|
88
|
+
if (devMountPath) {
|
|
89
|
+
if (!reloadTrimmed) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return language === 'python' ? normalizePythonReloadForComposeMounted(reloadTrimmed) : reloadTrimmed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (imageRunRaw) {
|
|
96
|
+
return normalizeComposeShellPortRef(imageRunRaw);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!reloadTrimmed) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (language === 'python') {
|
|
104
|
+
const imageCmd = derivePythonImageStartFromReload(reloadTrimmed);
|
|
105
|
+
return imageCmd ? `exec ${imageCmd}` : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
18
111
|
/**
|
|
19
112
|
* Resolve infra `pgpass` path: prefer system config dir; when home differs, allow legacy layout.
|
|
20
113
|
* @param {string|number} devId - Developer id (infra vs infra-dev{n})
|
|
@@ -119,14 +212,15 @@ async function loadComposeHead(deps, appName, appConfig, options) {
|
|
|
119
212
|
* @param {object} head - From loadComposeHead
|
|
120
213
|
* @returns {object}
|
|
121
214
|
*/
|
|
122
|
-
function buildComposeLayouts(deps, appName, appConfig, head) {
|
|
215
|
+
function buildComposeLayouts(deps, appName, appConfig, head, options) {
|
|
123
216
|
const { buildNetworkAndContainerNames, buildServiceConfig, buildVolumesConfig, buildNetworksConfig } = deps;
|
|
124
217
|
const { port, imageOverride, devId, idNum, scoped, remoteServer } = head;
|
|
125
218
|
const { networkName, containerName } = buildNetworkAndContainerNames(appName, devId, idNum, scoped);
|
|
126
219
|
const serviceConfig = buildServiceConfig(appName, appConfig, port, devId, {
|
|
127
220
|
imageOverride,
|
|
128
221
|
scopeOpts: scoped,
|
|
129
|
-
remoteServer
|
|
222
|
+
remoteServer,
|
|
223
|
+
omitAppTraefikLabels: options && options.omitAppTraefikLabels === true
|
|
130
224
|
});
|
|
131
225
|
const volumesConfig = buildVolumesConfig(appName);
|
|
132
226
|
const networksConfig = buildNetworksConfig(appConfig);
|
|
@@ -161,8 +255,13 @@ async function resolveComposePathsAndSecrets(ctx) {
|
|
|
161
255
|
);
|
|
162
256
|
const devMountPath = resolveDevMountPath(options);
|
|
163
257
|
const reloadRaw = appConfig.build?.reloadStart;
|
|
164
|
-
const
|
|
165
|
-
|
|
258
|
+
const language = appConfig.build?.language || appConfig.language || 'typescript';
|
|
259
|
+
const reloadStartCommand = buildReloadStartCommandForCompose(
|
|
260
|
+
language,
|
|
261
|
+
devMountPath,
|
|
262
|
+
reloadRaw,
|
|
263
|
+
appConfig.build
|
|
264
|
+
);
|
|
166
265
|
const infraPgpassPath = resolveInfraPgpassPath(devId, paths, fsSync.existsSync);
|
|
167
266
|
const useInfraPgpass = serviceCf.requiresDatabase && fsSync.existsSync(infraPgpassPath);
|
|
168
267
|
return {
|
|
@@ -185,7 +284,7 @@ async function resolveComposePathsAndSecrets(ctx) {
|
|
|
185
284
|
*/
|
|
186
285
|
async function generateDockerComposeImpl(deps, appName, appConfig, options) {
|
|
187
286
|
const head = await loadComposeHead(deps, appName, appConfig, options);
|
|
188
|
-
const layouts = buildComposeLayouts(deps, appName, appConfig, head);
|
|
287
|
+
const layouts = buildComposeLayouts(deps, appName, appConfig, head, options);
|
|
189
288
|
const side = await resolveComposePathsAndSecrets({
|
|
190
289
|
options,
|
|
191
290
|
appName,
|
|
@@ -213,4 +312,10 @@ function createGenerateDockerCompose(deps) {
|
|
|
213
312
|
return (appName, appConfig, options) => generateDockerComposeImpl(deps, appName, appConfig, options);
|
|
214
313
|
}
|
|
215
314
|
|
|
216
|
-
module.exports = {
|
|
315
|
+
module.exports = {
|
|
316
|
+
createGenerateDockerCompose,
|
|
317
|
+
resolveInfraPgpassPath,
|
|
318
|
+
derivePythonImageStartFromReload,
|
|
319
|
+
buildReloadStartCommandForCompose,
|
|
320
|
+
normalizePythonReloadForComposeMounted
|
|
321
|
+
};
|
|
@@ -282,10 +282,16 @@ function buildRequiresConfig(config) {
|
|
|
282
282
|
* @param {string} [runExtras.imageOverride] - Full image reference for run (e.g. from --image)
|
|
283
283
|
* @param {Object|null} [runExtras.scopeOpts] - Traefik / scoped compose options
|
|
284
284
|
* @param {string|null|undefined} [runExtras.remoteServer] - For ${REMOTE_HOST} in frontDoorRouting.host
|
|
285
|
+
* @param {boolean} [runExtras.omitAppTraefikLabels] - When true (user config `traefik: false`), omit Traefik labels and BASE_PATH env from compose even if application.yaml enables frontDoorRouting
|
|
285
286
|
* @returns {Object} Service configuration
|
|
286
287
|
*/
|
|
287
288
|
function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
|
|
288
|
-
const {
|
|
289
|
+
const {
|
|
290
|
+
imageOverride = null,
|
|
291
|
+
scopeOpts = null,
|
|
292
|
+
remoteServer = null,
|
|
293
|
+
omitAppTraefikLabels = false
|
|
294
|
+
} = runExtras;
|
|
289
295
|
const containerPortValue = getContainerPort(config, 3000);
|
|
290
296
|
const hostPort = port;
|
|
291
297
|
const useTraefikScope =
|
|
@@ -305,13 +311,16 @@ function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
|
|
|
305
311
|
containerPort: containerPortValue, // Container port (always set, equals containerPort if exists, else port)
|
|
306
312
|
hostPort: hostPort, // Host port (options.port if provided, else config.port)
|
|
307
313
|
healthCheck,
|
|
308
|
-
traefik:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
traefik:
|
|
315
|
+
omitAppTraefikLabels === true
|
|
316
|
+
? { enabled: false }
|
|
317
|
+
: buildTraefikConfig(
|
|
318
|
+
config,
|
|
319
|
+
devId,
|
|
320
|
+
scopeForHealthAndTraefik,
|
|
321
|
+
remoteServer,
|
|
322
|
+
healthCheck.path
|
|
323
|
+
),
|
|
315
324
|
...buildRequiresConfig(config)
|
|
316
325
|
};
|
|
317
326
|
}
|
|
@@ -42,8 +42,38 @@ function normalizeUrl(url) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* True when `config.yaml` has a non-expired-looking device entry for this controller URL
|
|
46
|
+
* (normalized host/path comparison).
|
|
47
|
+
*
|
|
48
|
+
* @async
|
|
49
|
+
* @param {string} controllerUrl
|
|
50
|
+
* @returns {Promise<boolean>}
|
|
51
|
+
*/
|
|
52
|
+
async function hasStoredDeviceTokenForController(controllerUrl) {
|
|
53
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const want = normalizeUrl(controllerUrl);
|
|
57
|
+
if (!want) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const userConfig = await config.getConfig();
|
|
62
|
+
if (!userConfig.device || typeof userConfig.device !== 'object') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return Object.keys(userConfig.device).some((key) => normalizeUrl(key) === want);
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get controller URL from logged-in user's device tokens.
|
|
73
|
+
* Prefers the entry under {@link config.controller} when it matches a `device` key; otherwise a
|
|
74
|
+
* single `device` entry; with multiple unrelated entries and no matching default, returns **null**
|
|
75
|
+
* (callers must not treat arbitrary key order as the active controller).
|
|
76
|
+
*
|
|
47
77
|
* @async
|
|
48
78
|
* @function getControllerUrlFromLoggedInUser
|
|
49
79
|
* @returns {Promise<string|null>} Controller URL from logged-in user, or null if not found
|
|
@@ -60,11 +90,23 @@ async function getControllerUrlFromLoggedInUser() {
|
|
|
60
90
|
return null;
|
|
61
91
|
}
|
|
62
92
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
const cfgController =
|
|
94
|
+
userConfig.controller && typeof userConfig.controller === 'string'
|
|
95
|
+
? normalizeUrl(userConfig.controller)
|
|
96
|
+
: '';
|
|
97
|
+
if (cfgController) {
|
|
98
|
+
const match = deviceUrls.find((u) => normalizeUrl(u) === cfgController);
|
|
99
|
+
if (match) {
|
|
100
|
+
return normalizeUrl(match);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (deviceUrls.length === 1) {
|
|
105
|
+
return normalizeUrl(deviceUrls[0]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
} catch {
|
|
68
110
|
return null;
|
|
69
111
|
}
|
|
70
112
|
}
|
|
@@ -109,6 +151,7 @@ async function resolveControllerUrl() {
|
|
|
109
151
|
|
|
110
152
|
module.exports = {
|
|
111
153
|
getDefaultControllerUrl,
|
|
154
|
+
hasStoredDeviceTokenForController,
|
|
112
155
|
getControllerUrlFromLoggedInUser,
|
|
113
156
|
getControllerFromConfig,
|
|
114
157
|
resolveControllerUrl
|
|
@@ -300,12 +300,20 @@ function appendValidationIssueLines(lines, envelope, maxIssues = 5) {
|
|
|
300
300
|
const code = iss && iss.code ? chalk.red(`[${iss.code}] `) : '';
|
|
301
301
|
const msg = iss && iss.message ? String(iss.message) : JSON.stringify(iss);
|
|
302
302
|
lines.push(` ${code}${chalk.yellow(msg)}`);
|
|
303
|
+
appendDpSec013Details(lines, iss);
|
|
303
304
|
}
|
|
304
305
|
if (issues.length > cap) {
|
|
305
306
|
lines.push(chalk.gray(` … and ${issues.length - cap} more (see --json or debug full/raw)`));
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
function appendDpSec013Details(lines, iss) {
|
|
311
|
+
if (!iss || iss.code !== 'DP-SEC-013') return;
|
|
312
|
+
const perm = iss.details && iss.details.resolvedPermission ? String(iss.details.resolvedPermission) : '';
|
|
313
|
+
if (!perm) return;
|
|
314
|
+
lines.push(` ${chalk.gray('Missing permission:')} ${chalk.white(perm)}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
309
317
|
/**
|
|
310
318
|
* @param {string[]} lines
|
|
311
319
|
* @param {Object} envelope
|
|
@@ -12,6 +12,7 @@ const path = require('path');
|
|
|
12
12
|
const readline = require('readline');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const { nodeFs } = require('../internal/node-fs');
|
|
15
|
+
const { formatSuccessLine } = require('./cli-layout-chalk');
|
|
15
16
|
|
|
16
17
|
const IPV4_RE = /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/;
|
|
17
18
|
|
|
@@ -238,7 +239,7 @@ async function resolveHostsIpForInit(hostname, hostsIp, logger) {
|
|
|
238
239
|
async function appendHostsBlockOrPrintManual(hostsPath, block, line, logger) {
|
|
239
240
|
try {
|
|
240
241
|
await nodeFs().promises.appendFile(hostsPath, block, { encoding: 'utf8' });
|
|
241
|
-
logger.log(chalk.green(
|
|
242
|
+
logger.log(chalk.green(' ') + formatSuccessLine(`Updated ${hostsPath}`) + '\n');
|
|
242
243
|
} catch (e) {
|
|
243
244
|
if (e.code === 'EACCES' || e.code === 'EPERM') {
|
|
244
245
|
logger.log(chalk.yellow(` ✖ Could not write ${hostsPath} (permission denied).`));
|
|
@@ -261,7 +262,7 @@ async function appendHostsBlockOrPrintManual(hostsPath, block, line, logger) {
|
|
|
261
262
|
async function tryWriteHostsEntry(hostsPath, hostnames, ip, skipConfirm, logger) {
|
|
262
263
|
const missing = hostnames.filter((h) => !hostsFileHasHostname(hostsPath, h));
|
|
263
264
|
if (missing.length === 0) {
|
|
264
|
-
logger.log(chalk.green(
|
|
265
|
+
logger.log(chalk.green(' ') + formatSuccessLine(`Required hostnames are already listed in ${hostsPath}. Nothing to do.`) + '\n');
|
|
265
266
|
return;
|
|
266
267
|
}
|
|
267
268
|
const line = `${ip} ${missing.join(' ')}`;
|
|
@@ -6,6 +6,7 @@ const chalk = require('chalk');
|
|
|
6
6
|
const config = require('../core/config');
|
|
7
7
|
const logger = require('./logger');
|
|
8
8
|
const { ensureDevSshConfigBlock } = require('./dev-ssh-config-helper');
|
|
9
|
+
const { successGlyph } = require('./cli-layout-chalk');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Hostname from Builder Server URL (for sync-ssh-host fallback).
|
|
@@ -46,7 +47,7 @@ async function mergeDevSshConfigAfterInit(baseUrl, devId) {
|
|
|
46
47
|
);
|
|
47
48
|
} else {
|
|
48
49
|
logger.log(
|
|
49
|
-
chalk.green('
|
|
50
|
+
`${chalk.green(' ')}${successGlyph()}${chalk.green(' SSH config updated: ')}` +
|
|
50
51
|
chalk.cyan(`Host ${res.hostAlias}`) +
|
|
51
52
|
chalk.gray(` → ${res.configPath}`)
|
|
52
53
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
1
|
+
const { formatSuccessLine, headerKeyValue, formatWarningLine } = require('./cli-test-layout-chalk');
|
|
2
2
|
/**
|
|
3
3
|
* Docker Build Utilities
|
|
4
4
|
*
|
|
@@ -177,7 +177,6 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
177
177
|
const spinner = ora({ text: 'Starting Docker build...', spinner: 'dots' }).start();
|
|
178
178
|
const fsSync = require('fs');
|
|
179
179
|
const path = require('path');
|
|
180
|
-
const { getRemoteDockerEnv } = require('./remote-docker-env');
|
|
181
180
|
dockerfilePath = path.resolve(dockerfilePath);
|
|
182
181
|
contextPath = path.resolve(contextPath);
|
|
183
182
|
|
|
@@ -196,7 +195,8 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
|
199
|
-
const
|
|
198
|
+
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
199
|
+
const dockerCliEnv = isTest ? { ...process.env } : await getDockerExecEnv();
|
|
200
200
|
const resolvedBuildArgs = buildArgs && typeof buildArgs === 'object' ? buildArgs : {};
|
|
201
201
|
return new Promise((resolve, reject) => {
|
|
202
202
|
runDockerBuildProcess({
|
|
@@ -207,7 +207,7 @@ async function executeDockerBuild(imageName, dockerfilePath, contextPath, tag, b
|
|
|
207
207
|
spinner,
|
|
208
208
|
resolve,
|
|
209
209
|
reject,
|
|
210
|
-
env:
|
|
210
|
+
env: dockerCliEnv,
|
|
211
211
|
buildArgs: resolvedBuildArgs,
|
|
212
212
|
noCache
|
|
213
213
|
});
|
|
@@ -258,14 +258,20 @@ async function executeBuild(imageName, dockerfilePath, contextPath, tag, options
|
|
|
258
258
|
*/
|
|
259
259
|
async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, options) {
|
|
260
260
|
const logger = require('../utils/logger');
|
|
261
|
-
const chalk = require('chalk');
|
|
262
261
|
|
|
263
|
-
logger.log(
|
|
264
|
-
logger.log(
|
|
262
|
+
logger.log(headerKeyValue('Dockerfile:', dockerfilePath));
|
|
263
|
+
logger.log(headerKeyValue('Build context:', contextPath));
|
|
265
264
|
|
|
266
265
|
await executeBuild(effectiveImageName, dockerfilePath, contextPath, tag, options);
|
|
267
266
|
|
|
268
|
-
|
|
267
|
+
const wantCompatTag =
|
|
268
|
+
effectiveImageName !== imageName &&
|
|
269
|
+
options &&
|
|
270
|
+
options.base === true;
|
|
271
|
+
if (!wantCompatTag) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
269
275
|
try {
|
|
270
276
|
const { promisify } = require('util');
|
|
271
277
|
const { exec } = require('child_process');
|
|
@@ -275,7 +281,9 @@ async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfi
|
|
|
275
281
|
await run(`docker tag ${effectiveImageName}:${tag} ${imageName}:${tag}`, { env });
|
|
276
282
|
logger.log(formatSuccessLine(`Tagged image: ${imageName}:${tag}`));
|
|
277
283
|
} catch (err) {
|
|
278
|
-
logger.log(
|
|
284
|
+
logger.log(
|
|
285
|
+
formatWarningLine(`Could not create compatibility tag ${imageName}:${tag} - ${err.message}`)
|
|
286
|
+
);
|
|
279
287
|
}
|
|
280
288
|
}
|
|
281
289
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether `aifabrix run --reload` can bind-mount workspace without Mutagen.
|
|
3
|
+
* Mutagen is only needed when the CLI filesystem and the Docker engine host differ.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Co-located Docker detection for reload mounts
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} host
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function normalizeHost(host) {
|
|
19
|
+
return String(host || '')
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/^\[|\]$/g, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} host
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isLocalLoopbackHost(host) {
|
|
30
|
+
const h = normalizeHost(host);
|
|
31
|
+
return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0:0:0:0:0:0:0:1';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} rest - after "tcp://"
|
|
36
|
+
* @returns {string|null}
|
|
37
|
+
*/
|
|
38
|
+
function tcpHostFromRest(rest) {
|
|
39
|
+
if (rest.startsWith('[')) {
|
|
40
|
+
const end = rest.indexOf(']');
|
|
41
|
+
if (end === -1) return null;
|
|
42
|
+
return normalizeHost(rest.slice(1, end));
|
|
43
|
+
}
|
|
44
|
+
const hostPort = rest.split('/')[0];
|
|
45
|
+
const colonIdx = hostPort.lastIndexOf(':');
|
|
46
|
+
if (colonIdx === -1) {
|
|
47
|
+
return normalizeHost(hostPort);
|
|
48
|
+
}
|
|
49
|
+
const maybePort = hostPort.slice(colonIdx + 1);
|
|
50
|
+
if (/^\d+$/.test(maybePort)) {
|
|
51
|
+
return normalizeHost(hostPort.slice(0, colonIdx));
|
|
52
|
+
}
|
|
53
|
+
return normalizeHost(hostPort);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract host from docker-endpoint (tcp, optional URL form).
|
|
58
|
+
* @param {string} endpoint
|
|
59
|
+
* @returns {string|null} normalized host, or null for unix socket paths / empty
|
|
60
|
+
*/
|
|
61
|
+
function extractHostFromDockerEndpoint(endpoint) {
|
|
62
|
+
const s = String(endpoint || '').trim();
|
|
63
|
+
if (!s) return null;
|
|
64
|
+
const lower = s.toLowerCase();
|
|
65
|
+
if (lower.startsWith('unix:')) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (lower.startsWith('tcp://')) {
|
|
69
|
+
return tcpHostFromRest(s.slice(6));
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const u = new URL(s.includes('://') ? s : `tcp://${s}`);
|
|
73
|
+
return normalizeHost(u.hostname);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* sync-ssh-host localhost check (same rules as run.js isLocalhostHost for reload gate).
|
|
81
|
+
* @param {string} host
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
function isLocalhostSyncSshHost(host) {
|
|
85
|
+
if (!host || typeof host !== 'string') return false;
|
|
86
|
+
const h = host.trim().toLowerCase();
|
|
87
|
+
return h === 'localhost' || h === '127.0.0.1';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* True when docker API host is this machine (first label or FQDN match).
|
|
92
|
+
* @param {string} dockerHost - from extractHostFromDockerEndpoint
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
function hostnameMatchesDockerHost(dockerHost) {
|
|
96
|
+
const h = normalizeHost(dockerHost);
|
|
97
|
+
if (!h) return true;
|
|
98
|
+
if (isLocalLoopbackHost(h)) return true;
|
|
99
|
+
const hn = normalizeHost(os.hostname());
|
|
100
|
+
if (h === hn) return true;
|
|
101
|
+
const hnShort = hn.split('.')[0];
|
|
102
|
+
const hShort = h.split('.')[0];
|
|
103
|
+
if (h === hnShort || hn === hShort) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* When true, use a direct bind mount for --reload; Mutagen is not required.
|
|
109
|
+
* @param {string|null|undefined} dockerEndpoint - config `docker-endpoint`
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
function isReloadBindMountOnEngineHost(dockerEndpoint) {
|
|
113
|
+
const e = String(dockerEndpoint || '').trim();
|
|
114
|
+
if (!e) return true;
|
|
115
|
+
if (e.toLowerCase().startsWith('unix:')) return true;
|
|
116
|
+
const host = extractHostFromDockerEndpoint(e);
|
|
117
|
+
if (host === null) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return hostnameMatchesDockerHost(host);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
extractHostFromDockerEndpoint,
|
|
125
|
+
isReloadBindMountOnEngineHost,
|
|
126
|
+
isLocalhostSyncSshHost
|
|
127
|
+
};
|