@aifabrix/builder 2.48.0 → 2.49.1
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/skills/README.md +14 -0
- package/jest.isolated-projects.js +3 -0
- package/jest.projects.js +12 -0
- package/lib/api/applications.api.js +23 -0
- package/lib/api/dev.api.js +1 -1
- package/lib/api/index.js +5 -2
- package/lib/api/types/dev.types.js +3 -2
- package/lib/app/deploy-config.js +6 -4
- package/lib/app/register.js +1 -1
- package/lib/app/run-container-start.js +0 -9
- package/lib/app/run-helpers.js +8 -4
- package/lib/app/run-reload-sync.js +16 -18
- package/lib/app/run-stop-containers.js +48 -5
- package/lib/app/run.js +6 -3
- package/lib/app/show.js +2 -2
- package/lib/cli/infra-guided-auth.js +50 -12
- package/lib/cli/infra-guided-footers.js +164 -0
- package/lib/cli/infra-guided-spinners.js +57 -0
- package/lib/cli/infra-guided.js +52 -181
- package/lib/cli/setup-app.js +1 -1
- package/lib/cli/setup-dev.js +3 -2
- package/lib/cli/setup-infra.js +3 -2
- package/lib/cli/setup-platform.js +3 -0
- package/lib/commands/login-device.js +6 -2
- package/lib/commands/login.js +4 -1
- package/lib/commands/setup-modes.js +19 -3
- package/lib/commands/setup-platform-auth.js +25 -0
- package/lib/commands/setup-prompts-admin.js +9 -0
- package/lib/commands/setup.js +60 -8
- package/lib/commands/teardown.js +3 -5
- package/lib/commands/up-common.js +8 -11
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +19 -4
- package/lib/commands/up-dataplane.js +155 -24
- package/lib/commands/up-miso.js +6 -2
- package/lib/core/secrets-env-declarative-expand.js +35 -23
- package/lib/core/setup-platform-mode.js +43 -0
- package/lib/deployment/deployer-status.js +26 -3
- package/lib/deployment/deployer.js +7 -14
- package/lib/external-system/download-helpers.js +10 -1
- package/lib/infrastructure/compose.js +40 -12
- package/lib/infrastructure/services.js +15 -3
- package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
- package/lib/utils/app-bootstrap-api.js +53 -0
- package/lib/utils/app-run-containers.js +14 -3
- package/lib/utils/applications-config-defaults.js +19 -3
- package/lib/utils/compose-generate-docker-compose.js +16 -5
- package/lib/utils/compose-generator.js +34 -15
- package/lib/utils/compose-traefik-ingress-base.js +13 -14
- package/lib/utils/config-paths.js +6 -1
- package/lib/utils/dev-user-groups.js +1 -0
- package/lib/utils/docker-daemon-tls-ca.js +94 -57
- package/lib/utils/docker-reload-mount.js +12 -1
- package/lib/utils/external-env-template.js +53 -18
- package/lib/utils/health-check-poll-ui.js +119 -0
- package/lib/utils/health-check-url.js +54 -34
- package/lib/utils/health-check.js +91 -35
- package/lib/utils/infra-optional-service-flags.js +31 -2
- package/lib/utils/mutagen.js +7 -4
- package/lib/utils/paths.js +26 -36
- package/lib/utils/platform-controller-url.js +23 -1
- package/lib/utils/refresh-remote-settings.js +67 -0
- package/lib/utils/remote-host-for-front-door.js +43 -0
- package/lib/utils/run-cli-flags.js +37 -1
- package/lib/utils/url-declarative-public-base.js +11 -4
- package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
- package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
- package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
- package/lib/utils/url-declarative-resolve.js +2 -0
- package/lib/utils/urls-local-registry-scan.js +95 -10
- package/lib/utils/urls-local-registry.js +24 -1
- package/lib/utils/worker-sidecars.js +2 -2
- package/lib/validation/external-system-auth-rules.js +45 -7
- package/package.json +2 -1
- package/scripts/test-dataplane-bootstrap.js +331 -0
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +24 -6
- package/templates/applications/dataplane/rbac.yaml +11 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +5 -0
- package/templates/infra/compose.yaml.hbs +13 -9
- package/templates/python/docker-compose.hbs +3 -0
- package/templates/typescript/docker-compose.hbs +3 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# aifabrix-builder Cursor skills
|
|
2
|
+
|
|
3
|
+
| Skill | Command | Purpose |
|
|
4
|
+
| ----- | ------- | ------- |
|
|
5
|
+
| [builder-push-github](builder-push-github/SKILL.md) | `/push-github` | Release branch CI + optional NPM deploy PR |
|
|
6
|
+
| [builder-repair-release](builder-repair-release/SKILL.md) | `/repair-release` | Version bump + CHANGELOG |
|
|
7
|
+
| [builder-validate-tests](builder-validate-tests/SKILL.md) | `/validate-tests` | Lint + Jest fix loop |
|
|
8
|
+
| [builder-validate-tests-ci](builder-validate-tests-ci/SKILL.md) | `/validate-tests.ci` | `build:ci` / GitHub parity |
|
|
9
|
+
| [builder-validate-code](builder-validate-code/SKILL.md) | `/validate-code` | Code analysis → improvement plans |
|
|
10
|
+
| [builder-validate-plan](builder-validate-plan/SKILL.md) | `/validate-plan` | Pre-implementation plan validation |
|
|
11
|
+
| [builder-validate-implementation](builder-validate-implementation/SKILL.md) | `/validate-implementation` | Post-implementation plan validation |
|
|
12
|
+
| [builder-validate-knowledgebase](builder-validate-knowledgebase/SKILL.md) | `/validate-knowledgebase` | `docs/` + schema validation |
|
|
13
|
+
|
|
14
|
+
Commands live in `.cursor/commands/`.
|
|
@@ -60,6 +60,8 @@ const ISOLATED_SUITE_ENTRIES = [
|
|
|
60
60
|
['secrets-ensure-catalog-fallback', ['**/tests/lib/core/secrets-ensure-catalog-fallback.test.js']],
|
|
61
61
|
['secrets-ensure', ['**/tests/lib/core/secrets-ensure.test.js']],
|
|
62
62
|
['url-declarative-vdir-inactive-env', ['**/tests/lib/utils/url-declarative-vdir-inactive-env.test.js']],
|
|
63
|
+
['url-declarative-resolve-load-doc', ['**/tests/lib/utils/url-declarative-resolve-load-doc.test.js']],
|
|
64
|
+
['compose-celery-worker', ['**/tests/lib/utils/compose-celery-worker.test.js']],
|
|
63
65
|
[
|
|
64
66
|
'url-declarative-user-cfg-per-app-proxy',
|
|
65
67
|
['**/tests/lib/utils/url-declarative-user-cfg-per-app-proxy.test.js']
|
|
@@ -79,6 +81,7 @@ const ISOLATED_SUITE_ENTRIES = [
|
|
|
79
81
|
['infra-kv-discovery', ['**/tests/lib/parameters/infra-kv-discovery.test.js']],
|
|
80
82
|
['infra-env-defaults', ['**/tests/lib/utils/infra-env-defaults.test.js']],
|
|
81
83
|
['compose-traefik-template', ['**/tests/lib/infrastructure/compose-traefik-template.test.js']],
|
|
84
|
+
['traefik-tls-dynamic', ['**/tests/lib/infrastructure/traefik-tls-dynamic.test.js']],
|
|
82
85
|
['generator-external-rbac', ['**/tests/lib/generator/generator-external-rbac.test.js']],
|
|
83
86
|
['helpers-ensure-admin-secrets', ['**/tests/lib/infrastructure/helpers-ensure-admin-secrets.test.js']],
|
|
84
87
|
['secrets-generator', ['**/tests/lib/utils/secrets-generator.test.js']],
|
package/jest.projects.js
CHANGED
|
@@ -156,6 +156,14 @@ const defaultProject = {
|
|
|
156
156
|
'\\\\tests\\\\lib\\\\utils\\\\url-declarative-vdir-inactive-env.test.js',
|
|
157
157
|
'lib/utils/url-declarative-vdir-inactive-env.test.js',
|
|
158
158
|
'url-declarative-vdir-inactive-env\\.test\\.js',
|
|
159
|
+
'/tests/lib/utils/url-declarative-resolve-load-doc.test.js',
|
|
160
|
+
'\\\\tests\\\\lib\\\\utils\\\\url-declarative-resolve-load-doc.test.js',
|
|
161
|
+
'lib/utils/url-declarative-resolve-load-doc.test.js',
|
|
162
|
+
'url-declarative-resolve-load-doc\\.test\\.js',
|
|
163
|
+
'/tests/lib/utils/compose-celery-worker.test.js',
|
|
164
|
+
'\\\\tests\\\\lib\\\\utils\\\\compose-celery-worker.test.js',
|
|
165
|
+
'lib/utils/compose-celery-worker.test.js',
|
|
166
|
+
'compose-celery-worker\\.test\\.js',
|
|
159
167
|
'/tests/lib/utils/url-declarative-user-cfg-per-app-proxy.test.js',
|
|
160
168
|
'\\\\tests\\\\lib\\\\utils\\\\url-declarative-user-cfg-per-app-proxy.test.js',
|
|
161
169
|
'lib/utils/url-declarative-user-cfg-per-app-proxy.test.js',
|
|
@@ -200,6 +208,10 @@ const defaultProject = {
|
|
|
200
208
|
'\\\\tests\\\\lib\\\\infrastructure\\\\compose-traefik-template.test.js',
|
|
201
209
|
'lib/infrastructure/compose-traefik-template.test.js',
|
|
202
210
|
'compose-traefik-template\\.test\\.js',
|
|
211
|
+
'/tests/lib/infrastructure/traefik-tls-dynamic.test.js',
|
|
212
|
+
'\\\\tests\\\\lib\\\\infrastructure\\\\traefik-tls-dynamic.test.js',
|
|
213
|
+
'lib/infrastructure/traefik-tls-dynamic.test.js',
|
|
214
|
+
'traefik-tls-dynamic\\.test\\.js',
|
|
203
215
|
'/tests/lib/utils/paths-app-listing.test.js',
|
|
204
216
|
'\\\\tests\\\\lib\\\\utils\\\\paths-app-listing.test.js',
|
|
205
217
|
'lib/utils/paths-app-listing.test.js',
|
|
@@ -142,6 +142,28 @@ async function registerApplication(controllerUrl, envKey, authConfig, registrati
|
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Bootstrap application in an environment (install-time, ENCRYPTION_KEY header).
|
|
147
|
+
* POST /api/v1/environments/{envKey}/applications/bootstrap
|
|
148
|
+
* @async
|
|
149
|
+
* @function bootstrapApplication
|
|
150
|
+
* @param {string} controllerUrl - Controller base URL
|
|
151
|
+
* @param {string} envKey - Environment key
|
|
152
|
+
* @param {string} encryptionKey - Shared ENCRYPTION_KEY (sent as x-encryption-key; never log)
|
|
153
|
+
* @param {Object} registrationData - Same body as register
|
|
154
|
+
* @returns {Promise<Object>} Bootstrap response
|
|
155
|
+
* @throws {Error} If bootstrap fails
|
|
156
|
+
*/
|
|
157
|
+
async function bootstrapApplication(controllerUrl, envKey, encryptionKey, registrationData) {
|
|
158
|
+
const client = new ApiClient(controllerUrl, {
|
|
159
|
+
type: 'bootstrap',
|
|
160
|
+
encryptionKey
|
|
161
|
+
});
|
|
162
|
+
return await client.post(`/api/v1/environments/${envKey}/applications/bootstrap`, {
|
|
163
|
+
body: registrationData
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
/**
|
|
146
168
|
* Rotate application secret
|
|
147
169
|
* POST /api/v1/environments/{envKey}/applications/{appKey}/rotate-secret
|
|
@@ -187,6 +209,7 @@ module.exports = {
|
|
|
187
209
|
updateApplication,
|
|
188
210
|
deleteApplication,
|
|
189
211
|
registerApplication,
|
|
212
|
+
bootstrapApplication,
|
|
190
213
|
rotateApplicationSecret,
|
|
191
214
|
getApplicationStatus
|
|
192
215
|
};
|
package/lib/api/dev.api.js
CHANGED
|
@@ -198,7 +198,7 @@ async function listUsers(serverUrl, clientCertPem, serverCaPem) {
|
|
|
198
198
|
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
199
199
|
* @param {string} serverUrl - Builder Server base URL
|
|
200
200
|
* @param {string} clientCertPem - PEM client certificate
|
|
201
|
-
* @param {Object} body - CreateUserDto: developerId, name, email, optional groups (admin | secret-manager | developer | docker)
|
|
201
|
+
* @param {Object} body - CreateUserDto: developerId, name, email, optional groups (admin | secret-manager | secret-reader | developer | docker)
|
|
202
202
|
* @param {string} [serverCaPem] - Dev root CA PEM
|
|
203
203
|
* @returns {Promise<Object>} UserResponseDto
|
|
204
204
|
*/
|
package/lib/api/index.js
CHANGED
|
@@ -17,8 +17,9 @@ class ApiClient {
|
|
|
17
17
|
* App endpoints receive token-only auth (Bearer). x-client-id/x-client-secret are used only for the controller token endpoint.
|
|
18
18
|
* @param {string} baseUrl - Base URL for the API (controller URL)
|
|
19
19
|
* @param {Object} [authConfig] - Authentication configuration
|
|
20
|
-
* @param {string} [authConfig.type] - Auth type ('bearer' | 'client-token' | 'client-credentials')
|
|
20
|
+
* @param {string} [authConfig.type] - Auth type ('bearer' | 'client-token' | 'client-credentials' | 'bootstrap')
|
|
21
21
|
* @param {string} [authConfig.token] - Bearer token
|
|
22
|
+
* @param {string} [authConfig.encryptionKey] - Shared ENCRYPTION_KEY (bootstrap only; never log)
|
|
22
23
|
* @param {string} [authConfig.clientId] - Client ID (client-credentials)
|
|
23
24
|
* @param {string} [authConfig.clientSecret] - Client secret (client-credentials)
|
|
24
25
|
*/
|
|
@@ -79,7 +80,9 @@ class ApiClient {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// User token (bearer) → Authorization: Bearer; application token (client-token) → x-client-token
|
|
82
|
-
if (this.authConfig.
|
|
83
|
+
if (this.authConfig.type === 'bootstrap' && this.authConfig.encryptionKey) {
|
|
84
|
+
headers['x-encryption-key'] = String(this.authConfig.encryptionKey);
|
|
85
|
+
} else if (this.authConfig.token) {
|
|
83
86
|
if (this.authConfig.type === 'client-token') {
|
|
84
87
|
headers['x-client-token'] = this.authConfig.token;
|
|
85
88
|
} else {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* @property {string} aifabrix-env-config - Env config path
|
|
32
32
|
* @property {string} remote-server - Builder-server base URL
|
|
33
33
|
* @property {string} docker-endpoint - Docker API endpoint
|
|
34
|
+
* @property {string} [developer-root-domain] - App hostname root (e.g. aifabrix.dev → dev01.aifabrix.dev)
|
|
34
35
|
* @property {boolean} [docker-tls-skip-verify] - When true, Docker uses DOCKER_TLS_VERIFY=0 even if ca.pem exists (client mTLS unchanged)
|
|
35
36
|
* @property {string} sync-ssh-user - SSH user for Mutagen
|
|
36
37
|
* @property {string} sync-ssh-host - SSH host for Mutagen
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
* @property {string} createdAt - ISO 8601
|
|
46
47
|
* @property {boolean} certificateIssued - Whether cert was issued
|
|
47
48
|
* @property {string} [certificateValidNotAfter] - Cert validity end (optional)
|
|
48
|
-
* @property {string[]} groups - Access groups (admin, secret-manager, developer, docker)
|
|
49
|
+
* @property {string[]} groups - Access groups (admin, secret-manager, secret-reader, developer, docker)
|
|
49
50
|
*/
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
* @property {string} developerId - Unique developer ID (numeric string)
|
|
55
56
|
* @property {string} name - Display name
|
|
56
57
|
* @property {string} email - Email
|
|
57
|
-
* @property {string[]} [groups] - Default [developer]; tokens: admin, secret-manager, developer, docker
|
|
58
|
+
* @property {string[]} [groups] - Default [developer]; tokens: admin, secret-manager, secret-reader, developer, docker
|
|
58
59
|
*/
|
|
59
60
|
|
|
60
61
|
/**
|
package/lib/app/deploy-config.js
CHANGED
|
@@ -80,7 +80,8 @@ async function extractDeploymentConfig(options, _variables) {
|
|
|
80
80
|
repositoryUrl: options.repositoryUrl,
|
|
81
81
|
// Omit default here so deployer can resolve 2s vs 5s from controller deploymentType (GET /api/v1/health)
|
|
82
82
|
pollInterval: Number.isFinite(pollIntervalExplicit) ? pollIntervalExplicit : undefined,
|
|
83
|
-
pollMaxAttempts: options.pollMaxAttempts || 60
|
|
83
|
+
pollMaxAttempts: options.pollMaxAttempts || 60,
|
|
84
|
+
deploymentAuth: options.deploymentAuth
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -118,7 +119,7 @@ async function configureDeploymentEnvironment(_options, deploymentConfig) {
|
|
|
118
119
|
* @returns {Promise<void>}
|
|
119
120
|
* @throws {Error} If authentication fails
|
|
120
121
|
*/
|
|
121
|
-
async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
122
|
+
async function refreshDeploymentToken(appName, deploymentConfig, options = {}) {
|
|
122
123
|
if (!deploymentConfig.controllerUrl) {
|
|
123
124
|
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
124
125
|
}
|
|
@@ -127,7 +128,8 @@ async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
|
127
128
|
const authConfig = await getDeploymentAuth(
|
|
128
129
|
deploymentConfig.controllerUrl,
|
|
129
130
|
deploymentConfig.envKey,
|
|
130
|
-
appName
|
|
131
|
+
appName,
|
|
132
|
+
{ deploymentAuth: options.deploymentAuth || deploymentConfig.deploymentAuth }
|
|
131
133
|
);
|
|
132
134
|
if (!authConfig || !authConfig.controller) {
|
|
133
135
|
throw new Error('Invalid authentication configuration: missing controller URL');
|
|
@@ -159,7 +161,7 @@ async function loadDeploymentConfig(appName, options) {
|
|
|
159
161
|
const deploymentConfig = await extractDeploymentConfig(options, variables);
|
|
160
162
|
|
|
161
163
|
await configureDeploymentEnvironment(options, deploymentConfig);
|
|
162
|
-
await refreshDeploymentToken(appName, deploymentConfig);
|
|
164
|
+
await refreshDeploymentToken(appName, deploymentConfig, options);
|
|
163
165
|
|
|
164
166
|
validateDeploymentConfig(deploymentConfig);
|
|
165
167
|
|
package/lib/app/register.js
CHANGED
|
@@ -199,5 +199,5 @@ async function registerApplication(appKey, options = {}) {
|
|
|
199
199
|
displayRegistrationResults(responseData, authConfig.apiUrl, environment, registrationData.displayName);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
module.exports = { registerApplication, getEnvironmentPrefix };
|
|
202
|
+
module.exports = { registerApplication, getEnvironmentPrefix, buildRegistrationData, saveLocalCredentials, applyLocalhostPortAdjustment };
|
|
203
203
|
|
|
@@ -112,15 +112,6 @@ async function waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, o) {
|
|
|
112
112
|
logger.log(formatSuccessLine(`Container ${containerName} started`));
|
|
113
113
|
await containerHelpers.logContainerStatus(containerName, debug);
|
|
114
114
|
|
|
115
|
-
const healthCheckPath = appConfig?.healthCheck?.path || '/health';
|
|
116
|
-
const healthUrl = (typeof healthCheck.computeHealthCheckUrl === 'function')
|
|
117
|
-
? await healthCheck.computeHealthCheckUrl(appName, port, appConfig, { runOptions: ro })
|
|
118
|
-
: null;
|
|
119
|
-
// Keep a stable fallback in the message if URL computation fails for any reason.
|
|
120
|
-
const displayUrl = (healthUrl && typeof healthUrl === 'string')
|
|
121
|
-
? healthUrl
|
|
122
|
-
: `http://localhost:${port}${healthCheckPath}`;
|
|
123
|
-
logger.log(formatProgress(`Waiting for healthy: ${displayUrl}…`));
|
|
124
115
|
await healthCheck.waitForHealthCheck(appName, 90, port, appConfig, debug, ro);
|
|
125
116
|
|
|
126
117
|
for (const p of [runEnvPath, runEnvAdminPath]) {
|
package/lib/app/run-helpers.js
CHANGED
|
@@ -290,8 +290,10 @@ function calculateComposePort(options, appConfig, developerId) {
|
|
|
290
290
|
* @param {string} devDir - Dev directory path
|
|
291
291
|
* @returns {Promise<string>} Path to compose file
|
|
292
292
|
*/
|
|
293
|
-
async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
|
|
294
|
-
|
|
293
|
+
async function generateComposeFile(appName, appConfig, composeOptions, devDir, logOpts = {}) {
|
|
294
|
+
if (!logOpts.suppressProgressLog) {
|
|
295
|
+
logger.log(formatProgress('Generating Docker Compose configuration…'));
|
|
296
|
+
}
|
|
295
297
|
const composeContent = await composeGenerator.generateDockerCompose(appName, appConfig, composeOptions);
|
|
296
298
|
runEnvCompose.assertNoPasswordLiteralsInCompose(composeContent);
|
|
297
299
|
const tempComposePath = path.join(devDir, 'docker-compose.yaml');
|
|
@@ -352,7 +354,7 @@ async function prepareEnvironment(appName, appConfig, options) {
|
|
|
352
354
|
);
|
|
353
355
|
|
|
354
356
|
runEnvCompose.cleanApplicationsDir(developerId);
|
|
355
|
-
logger.log(formatProgress('
|
|
357
|
+
logger.log(formatProgress('Preparing run environment (env + compose)…'));
|
|
356
358
|
const { runEnvPath, runEnvAdminPath } = await runEnvCompose.buildMergedRunEnvAndWrite(
|
|
357
359
|
appName,
|
|
358
360
|
appConfig,
|
|
@@ -370,7 +372,9 @@ async function prepareEnvironment(appName, appConfig, options) {
|
|
|
370
372
|
omitAppTraefikLabels: userCfg.traefik === false
|
|
371
373
|
};
|
|
372
374
|
composeOptions.port = calculateComposePort(composeOptions, appConfig, developerId);
|
|
373
|
-
const composePath = await generateComposeFile(appName, appConfig, composeOptions, devDir
|
|
375
|
+
const composePath = await generateComposeFile(appName, appConfig, composeOptions, devDir, {
|
|
376
|
+
suppressProgressLog: true
|
|
377
|
+
});
|
|
374
378
|
|
|
375
379
|
await writeEnvOutputIfConfigured(appName, appConfig, runEnvPath, options);
|
|
376
380
|
|
|
@@ -9,16 +9,15 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { formatProgress } = require('../utils/cli-test-layout-chalk');
|
|
12
|
+
const { formatProgress, metadata } = require('../utils/cli-test-layout-chalk');
|
|
13
13
|
const config = require('../core/config');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
15
|
const mutagen = require('../utils/mutagen');
|
|
16
16
|
const pathsUtil = require('../utils/paths');
|
|
17
17
|
const {
|
|
18
18
|
isReloadBindMountOnEngineHost,
|
|
19
|
-
|
|
19
|
+
isHostOnThisMachine
|
|
20
20
|
} = require('../utils/docker-reload-mount');
|
|
21
|
-
const { sectionTitle, headerKeyValue, metadata } = require('../utils/cli-test-layout-chalk');
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* @typedef {Object} ReloadSyncSummaryBind
|
|
@@ -109,9 +108,11 @@ async function ensureReloadSync(appName, developerId, debug, codePath, remoteSyn
|
|
|
109
108
|
return { transport: 'bind-mount', hostPath: codePath };
|
|
110
109
|
}
|
|
111
110
|
const syncSshHost = await config.getSyncSshHost();
|
|
112
|
-
if (
|
|
111
|
+
if (isHostOnThisMachine(syncSshHost || '')) {
|
|
113
112
|
if (debug) {
|
|
114
|
-
logger.log(
|
|
113
|
+
logger.log(
|
|
114
|
+
chalk.gray('[DEBUG] sync-ssh-host is this machine; skipping Mutagen, using local path')
|
|
115
|
+
);
|
|
115
116
|
}
|
|
116
117
|
return { transport: 'bind-mount', hostPath: codePath };
|
|
117
118
|
}
|
|
@@ -127,22 +128,19 @@ function logReloadDevSummary(reload, summary) {
|
|
|
127
128
|
if (!reload || !summary) {
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
130
|
-
logger.log('');
|
|
131
|
-
logger.log(sectionTitle('Reload (dev)'));
|
|
132
131
|
if (summary.transport === 'bind-mount') {
|
|
133
|
-
logger.log(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
logger.log(
|
|
133
|
+
metadata(
|
|
134
|
+
'Reload (dev) — Edits under the host path are visible inside the container immediately.'
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
|
-
logger.log(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
logger.log(metadata(`Mutagen endpoint: ${summary.sshUrl}`));
|
|
145
|
-
logger.log('');
|
|
139
|
+
logger.log(
|
|
140
|
+
metadata(
|
|
141
|
+
`Reload (dev) — Mutagen sync (${summary.sessionName}); edits sync to ${summary.remotePath} before the container sees them.`
|
|
142
|
+
)
|
|
143
|
+
);
|
|
146
144
|
}
|
|
147
145
|
|
|
148
146
|
module.exports = { ensureReloadSync, logReloadDevSummary };
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
formatProgress,
|
|
13
|
+
formatSuccessLine
|
|
14
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
12
15
|
const config = require('../core/config');
|
|
13
16
|
const logger = require('../utils/logger');
|
|
14
17
|
const containerHelpers = require('../utils/app-run-containers');
|
|
@@ -16,6 +19,34 @@ const helpers = require('./run-helpers');
|
|
|
16
19
|
const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
|
|
17
20
|
const { getCeleryCompanionAppKeys } = require('../utils/celery-companion-apps');
|
|
18
21
|
|
|
22
|
+
function shouldUseReplaceContainerSpinner() {
|
|
23
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} progressText
|
|
28
|
+
* @param {string} successText
|
|
29
|
+
* @param {() => Promise<void>} work
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
async function runContainerReplaceLine(progressText, successText, work) {
|
|
33
|
+
if (shouldUseReplaceContainerSpinner()) {
|
|
34
|
+
const ora = require('ora');
|
|
35
|
+
const spinner = ora({ text: progressText, spinner: 'dots' }).start();
|
|
36
|
+
try {
|
|
37
|
+
await work();
|
|
38
|
+
spinner.succeed(successText);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail(progressText.replace(/…$/, '').trim());
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
logger.log(formatProgress(progressText));
|
|
46
|
+
await work();
|
|
47
|
+
logger.log(formatSuccessLine(successText));
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
/**
|
|
20
51
|
* @param {Object} appConfig
|
|
21
52
|
* @param {Object} options
|
|
@@ -62,8 +93,14 @@ async function findRunningCompanionKeys(companionKeys, developerId, debug, scope
|
|
|
62
93
|
async function stopCompanionContainers(companionKeys, developerId, debug, scopeOpts) {
|
|
63
94
|
for (const companionKey of companionKeys) {
|
|
64
95
|
const companionName = containerHelpers.getContainerName(companionKey, developerId, scopeOpts);
|
|
65
|
-
|
|
66
|
-
|
|
96
|
+
await runContainerReplaceLine(
|
|
97
|
+
`Stopping companion container ${companionName}…`,
|
|
98
|
+
`Companion container ${companionName} stopped and removed`,
|
|
99
|
+
() =>
|
|
100
|
+
helpers.stopAndRemoveContainer(companionKey, developerId, debug, scopeOpts, {
|
|
101
|
+
suppressLog: true
|
|
102
|
+
})
|
|
103
|
+
);
|
|
67
104
|
}
|
|
68
105
|
}
|
|
69
106
|
|
|
@@ -101,8 +138,14 @@ async function checkAndStopContainer(appName, appConfig, options, debug) {
|
|
|
101
138
|
return;
|
|
102
139
|
}
|
|
103
140
|
const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
await runContainerReplaceLine(
|
|
142
|
+
`Replacing running container ${containerName}…`,
|
|
143
|
+
`Container ${containerName} stopped and removed`,
|
|
144
|
+
() =>
|
|
145
|
+
helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts, {
|
|
146
|
+
suppressLog: true
|
|
147
|
+
})
|
|
148
|
+
);
|
|
106
149
|
}
|
|
107
150
|
|
|
108
151
|
module.exports = {
|
package/lib/app/run.js
CHANGED
|
@@ -29,7 +29,7 @@ const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
|
|
|
29
29
|
const { logRestartDevMountSummary } = require('./restart-display');
|
|
30
30
|
const { checkAndStopContainer } = require('./run-stop-containers');
|
|
31
31
|
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
32
|
-
const {
|
|
32
|
+
const { resolveRunProxyPersistEnabled, resolveRunEffectiveNoProxy } = require('../utils/run-cli-flags');
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* True if host is localhost or 127.0.0.1 (case-insensitive).
|
|
@@ -347,7 +347,10 @@ async function prepareAppRun(appName, options, debug) {
|
|
|
347
347
|
}
|
|
348
348
|
let userCfg = await config.getConfig();
|
|
349
349
|
const appsDefaults = require('../utils/applications-config-defaults');
|
|
350
|
-
await appsDefaults.persistApplicationRunProxyFlag(
|
|
350
|
+
await appsDefaults.persistApplicationRunProxyFlag(
|
|
351
|
+
appName,
|
|
352
|
+
resolveRunProxyPersistEnabled(options, userCfg)
|
|
353
|
+
);
|
|
351
354
|
if (envKey === 'dev') {
|
|
352
355
|
await appsDefaults.persistApplicationReloadFlag(appName, options.reload === true);
|
|
353
356
|
}
|
|
@@ -356,7 +359,7 @@ async function prepareAppRun(appName, options, debug) {
|
|
|
356
359
|
const mergedOpts = {
|
|
357
360
|
...options,
|
|
358
361
|
reload: options.reload === true,
|
|
359
|
-
noProxy:
|
|
362
|
+
noProxy: resolveRunEffectiveNoProxy(options, userCfg, savedProxyHint)
|
|
360
363
|
};
|
|
361
364
|
logger.log('');
|
|
362
365
|
logger.log(sectionTitle('Run'));
|
package/lib/app/show.js
CHANGED
|
@@ -45,11 +45,11 @@ const {
|
|
|
45
45
|
* @param {Object} userCfg
|
|
46
46
|
*/
|
|
47
47
|
function attachRunDefaultsFromUserConfig(summary, appKey, userCfg) {
|
|
48
|
-
const { isApplicationsReloadDefaultOn,
|
|
48
|
+
const { isApplicationsReloadDefaultOn, getEffectiveApplicationsRunProxyHint } = require('../utils/applications-config-defaults');
|
|
49
49
|
if (isApplicationsReloadDefaultOn(userCfg, appKey)) {
|
|
50
50
|
summary.runReloadDefault = true;
|
|
51
51
|
}
|
|
52
|
-
summary.runProxyDefault =
|
|
52
|
+
summary.runProxyDefault = getEffectiveApplicationsRunProxyHint(userCfg, appKey);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -16,6 +16,7 @@ const { withMutedLogger } = require('../utils/with-muted-logger');
|
|
|
16
16
|
const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
|
|
17
17
|
const { isAuthenticationError } = require('../utils/cli-utils');
|
|
18
18
|
const { resolveControllerUrl, isPlatformAuthValidForController } = require('../utils/controller-url');
|
|
19
|
+
const { canUseBootstrapProvision } = require('../commands/up-dataplane-bootstrap');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @param {Object} authOpts
|
|
@@ -59,7 +60,10 @@ async function runGuidedDeviceLogin(handleLoginFn, controllerUrl, opts = {}) {
|
|
|
59
60
|
compact: true,
|
|
60
61
|
skipControllerUrlPersist: Boolean(opts.tokenControllerUrl),
|
|
61
62
|
skipDataplaneVersionRefresh: opts.skipDataplaneVersionRefresh === true,
|
|
62
|
-
exitOnFailure: false
|
|
63
|
+
exitOnFailure: false,
|
|
64
|
+
guidedInstall: opts.guidedInstall === true,
|
|
65
|
+
onProgressText: opts.onProgressText,
|
|
66
|
+
suppressSuccessLine: opts.suppressSuccessLine === true
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -74,12 +78,17 @@ async function runGuidedAuthStep(handleLoginFn, authOpts, spinners) {
|
|
|
74
78
|
const spin = spinners.startSpinner('Authenticating...');
|
|
75
79
|
const { apiUrl, tokenUrl } = await resolveGuidedAuthControllerUrls(authOpts);
|
|
76
80
|
const loginOpts = { skipDataplaneVersionRefresh: true, tokenControllerUrl: tokenUrl };
|
|
81
|
+
const bootstrapEligible = await canUseBootstrapProvision({ skipInfraCheck: true });
|
|
77
82
|
const skipLogin =
|
|
83
|
+
bootstrapEligible ||
|
|
78
84
|
authOpts.skipLoginIfAuthenticated === true ||
|
|
79
85
|
(await isPlatformAuthValidForController(tokenUrl));
|
|
80
86
|
try {
|
|
81
87
|
if (skipLogin) {
|
|
82
|
-
spinners.stopSpinnerSuccess(
|
|
88
|
+
spinners.stopSpinnerSuccess(
|
|
89
|
+
spin,
|
|
90
|
+
bootstrapEligible ? 'Bootstrap provision (no device login)' : 'Already authenticated'
|
|
91
|
+
);
|
|
83
92
|
return;
|
|
84
93
|
}
|
|
85
94
|
await runGuidedDeviceLogin(handleLoginFn, apiUrl, loginOpts);
|
|
@@ -103,24 +112,53 @@ async function runGuidedAuthStep(handleLoginFn, authOpts, spinners) {
|
|
|
103
112
|
* @param {Function} handleUpDataplane
|
|
104
113
|
* @param {Function} handleLoginFn
|
|
105
114
|
* @param {string} controllerUrl
|
|
115
|
+
* @param {{ onSpinnerText?: (text: string) => void }} [ui]
|
|
106
116
|
* @returns {Promise<void>}
|
|
107
117
|
*/
|
|
108
|
-
async function runGuidedDataplaneWithAuthRecovery(
|
|
118
|
+
async function runGuidedDataplaneWithAuthRecovery(
|
|
119
|
+
options,
|
|
120
|
+
handleUpDataplane,
|
|
121
|
+
handleLoginFn,
|
|
122
|
+
controllerUrl,
|
|
123
|
+
ui = {}
|
|
124
|
+
) {
|
|
125
|
+
const onSpinnerText = typeof ui.onSpinnerText === 'function' ? ui.onSpinnerText : null;
|
|
126
|
+
const dataplaneOpts = {
|
|
127
|
+
...options,
|
|
128
|
+
platformInstall: true,
|
|
129
|
+
skipInfraCheck: true,
|
|
130
|
+
...(onSpinnerText ? { onPollText: onSpinnerText } : {})
|
|
131
|
+
};
|
|
109
132
|
try {
|
|
110
|
-
await withMutedLogger(() =>
|
|
111
|
-
handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
|
|
112
|
-
);
|
|
133
|
+
await withMutedLogger(() => handleUpDataplane(dataplaneOpts));
|
|
113
134
|
} catch (error) {
|
|
114
135
|
if (!isAuthenticationError(error)) {
|
|
115
136
|
throw error;
|
|
116
137
|
}
|
|
138
|
+
const bootstrapEligible = await canUseBootstrapProvision({
|
|
139
|
+
skipInfraCheck: true,
|
|
140
|
+
allowNonDev: options.allowNonDev
|
|
141
|
+
});
|
|
142
|
+
if (bootstrapEligible) {
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
117
145
|
const loginUrl = error.controllerUrl || controllerUrl || (await resolveControllerUrl());
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
if (onSpinnerText) {
|
|
147
|
+
onSpinnerText('Authenticating...');
|
|
148
|
+
} else {
|
|
149
|
+
logger.log('');
|
|
150
|
+
logger.log(chalk.yellow('Device login required to register and deploy Dataplane.'));
|
|
151
|
+
}
|
|
152
|
+
await runGuidedDeviceLogin(handleLoginFn, loginUrl, {
|
|
153
|
+
skipDataplaneVersionRefresh: false,
|
|
154
|
+
compact: true,
|
|
155
|
+
guidedInstall: true,
|
|
156
|
+
onProgressText: onSpinnerText || undefined
|
|
157
|
+
});
|
|
158
|
+
if (onSpinnerText) {
|
|
159
|
+
onSpinnerText('Validating Dataplane...');
|
|
160
|
+
}
|
|
161
|
+
await withMutedLogger(() => handleUpDataplane(dataplaneOpts));
|
|
124
162
|
}
|
|
125
163
|
}
|
|
126
164
|
|