@aifabrix/builder 2.49.0 → 2.49.2

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.
Files changed (35) hide show
  1. package/.cursor/process/README.md +9 -0
  2. package/.cursor/skills/README.md +14 -0
  3. package/lib/api/dev.api.js +1 -0
  4. package/lib/api/host-ops.api.js +69 -0
  5. package/lib/cli/infra-guided-auth.js +8 -6
  6. package/lib/cli/infra-guided.js +11 -2
  7. package/lib/cli/setup-dev-path-commands.js +24 -0
  8. package/lib/cli/setup-dev.js +108 -14
  9. package/lib/commands/dev-cli-handlers.js +64 -3
  10. package/lib/commands/dev-host-ops.js +239 -0
  11. package/lib/commands/teardown.js +3 -5
  12. package/lib/commands/up-common.js +8 -11
  13. package/lib/commands/up-dataplane.js +81 -13
  14. package/lib/commands/up-miso.js +6 -2
  15. package/lib/schema/external-system.schema.json +114 -2
  16. package/lib/utils/health-check-poll-ui.js +63 -28
  17. package/lib/utils/health-check.js +50 -21
  18. package/lib/utils/paths.js +26 -36
  19. package/package.json +1 -1
  20. package/templates/applications/dataplane/application.yaml +1 -1
  21. package/templates/applications/dataplane/env.template +2 -1
  22. package/templates/applications/miso-controller/env.template +2 -0
  23. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +0 -1
  24. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +0 -17
  25. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +0 -1
  26. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +0 -1
  27. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +0 -1
  28. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +0 -17
  29. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +0 -17
  30. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +0 -17
  31. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +0 -17
  32. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +0 -17
  33. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +0 -17
  34. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +0 -17
  35. package/.temp/validation/summary.txt +0 -5
@@ -0,0 +1,9 @@
1
+ # Process documentation
2
+
3
+ Canonical process descriptions for aifabrix-builder automation and release workflows.
4
+
5
+ | Process | Description |
6
+ | ------- | ----------- |
7
+ | [deployment.md](./deployment.md) | NPM release: preflight, release branch CI, PR to `main`, publish and GitHub release |
8
+
9
+ Command specs (agent-executable) live under [`.cursor/commands/`](../commands/).
@@ -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/`.
@@ -391,6 +391,7 @@ async function deleteSecret(serverUrl, clientCertPem, key, serverCaPem, secretsE
391
391
  }
392
392
 
393
393
  module.exports = {
394
+ request,
394
395
  issueCert,
395
396
  getHealth,
396
397
  getSettings,
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @fileoverview Builder Server host-ops API
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ const devApi = require('./dev.api');
8
+
9
+ /**
10
+ * Create host-ops job. POST /api/dev/host-ops/jobs
11
+ * @param {string} serverUrl
12
+ * @param {string} clientCertPem
13
+ * @param {Object} body - { type, params }
14
+ * @param {string} [serverCaPem]
15
+ * @returns {Promise<{ jobId: string, status: string }>}
16
+ */
17
+ async function createHostOpsJob(serverUrl, clientCertPem, body, serverCaPem) {
18
+ const base = devApi.normalizeBaseUrl(serverUrl);
19
+ return devApi.request(devApi.buildUrl(base, '/api/dev/host-ops/jobs'), {
20
+ method: 'POST',
21
+ headers: { 'X-Client-Cert': devApi.encodeCertForHeader(clientCertPem) },
22
+ body,
23
+ serverCaPem
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Get host-ops job. GET /api/dev/host-ops/jobs/:jobId
29
+ * @param {string} serverUrl
30
+ * @param {string} clientCertPem
31
+ * @param {string} jobId
32
+ * @param {string} [serverCaPem]
33
+ * @returns {Promise<Object>}
34
+ */
35
+ async function getHostOpsJob(serverUrl, clientCertPem, jobId, serverCaPem) {
36
+ const base = devApi.normalizeBaseUrl(serverUrl);
37
+ return devApi.request(
38
+ devApi.buildUrl(base, `/api/dev/host-ops/jobs/${encodeURIComponent(jobId)}`),
39
+ {
40
+ method: 'GET',
41
+ headers: { 'X-Client-Cert': devApi.encodeCertForHeader(clientCertPem) },
42
+ serverCaPem
43
+ }
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Queue preflight (check-only job). POST /api/dev/host-ops/preflight
49
+ * @param {string} serverUrl
50
+ * @param {string} clientCertPem
51
+ * @param {Object} body - { type, params }
52
+ * @param {string} [serverCaPem]
53
+ * @returns {Promise<{ jobId: string, status: string }>}
54
+ */
55
+ async function preflightHostOps(serverUrl, clientCertPem, body, serverCaPem) {
56
+ const base = devApi.normalizeBaseUrl(serverUrl);
57
+ return devApi.request(devApi.buildUrl(base, '/api/dev/host-ops/preflight'), {
58
+ method: 'POST',
59
+ headers: { 'X-Client-Cert': devApi.encodeCertForHeader(clientCertPem) },
60
+ body,
61
+ serverCaPem
62
+ });
63
+ }
64
+
65
+ module.exports = {
66
+ createHostOpsJob,
67
+ getHostOpsJob,
68
+ preflightHostOps
69
+ };
@@ -123,10 +123,14 @@ async function runGuidedDataplaneWithAuthRecovery(
123
123
  ui = {}
124
124
  ) {
125
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
+ };
126
132
  try {
127
- await withMutedLogger(() =>
128
- handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
129
- );
133
+ await withMutedLogger(() => handleUpDataplane(dataplaneOpts));
130
134
  } catch (error) {
131
135
  if (!isAuthenticationError(error)) {
132
136
  throw error;
@@ -154,9 +158,7 @@ async function runGuidedDataplaneWithAuthRecovery(
154
158
  if (onSpinnerText) {
155
159
  onSpinnerText('Validating Dataplane...');
156
160
  }
157
- await withMutedLogger(() =>
158
- handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
159
- );
161
+ await withMutedLogger(() => handleUpDataplane(dataplaneOpts));
160
162
  }
161
163
  }
162
164
 
@@ -104,6 +104,9 @@ async function waitForAppReady(appName, opts = {}) {
104
104
  if (userCfg && userCfg.traefik === true) {
105
105
  healthRunOpts.traefikEnabled = true;
106
106
  }
107
+ if (typeof opts.onPollText === 'function') {
108
+ healthRunOpts.onPollText = opts.onPollText;
109
+ }
107
110
  await healthCheck.waitForHealthCheck(appName, timeoutSeconds, hostPort, appConfig, false, healthRunOpts);
108
111
  }
109
112
 
@@ -180,7 +183,10 @@ async function runGuidedUpMiso(options, handleUpMiso) {
180
183
  stopSpinnerSuccess(kcSpin, 'Keycloak ready');
181
184
  logger.log('');
182
185
  const mcSpin = startSpinner('Starting Miso Controller...');
183
- await waitForAppReady('miso-controller', { timeoutSeconds: 120 });
186
+ await waitForAppReady('miso-controller', {
187
+ timeoutSeconds: 120,
188
+ onPollText: createSpinnerTextUpdater(mcSpin)
189
+ });
184
190
  stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
185
191
  await logMisoReadyFooter();
186
192
  await validateAppErrorLogs(['miso-controller']);
@@ -242,7 +248,10 @@ async function runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso) {
242
248
  logger.log('');
243
249
  emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
244
250
  const mcSpin = startSpinner('Starting Miso Controller...');
245
- await waitForAppReady('miso-controller', { timeoutSeconds: 120 });
251
+ await waitForAppReady('miso-controller', {
252
+ timeoutSeconds: 120,
253
+ onPollText: createSpinnerTextUpdater(mcSpin)
254
+ });
246
255
  stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
247
256
  logger.log('');
248
257
  }
@@ -44,6 +44,29 @@ async function runShellEnvRegistration() {
44
44
  }
45
45
  }
46
46
 
47
+ function addSetEnvConfigCommand(dev) {
48
+ dev
49
+ .command('set-env-config <path>')
50
+ .description('Set or clear aifabrix-env-config path in ~/.aifabrix/config.yaml')
51
+ .action(async(envConfigPath) => {
52
+ try {
53
+ const trimmed = (envConfigPath || '').trim();
54
+ await config.setAifabrixEnvConfigPath(trimmed);
55
+ logger.log(
56
+ trimmed === ''
57
+ ? formatSuccessLine('Env config path cleared')
58
+ : formatSuccessLine(`Env config path set to ${trimmed}`)
59
+ );
60
+ const devId = await config.getDeveloperId();
61
+ const { displayDevConfig } = require('../commands/dev-show-display');
62
+ await displayDevConfig(devId);
63
+ } catch (error) {
64
+ handleCommandError(error, 'dev set-env-config');
65
+ process.exit(1);
66
+ }
67
+ });
68
+ }
69
+
47
70
  function addSetHomeCommand(dev) {
48
71
  dev
49
72
  .command('set-home <path>')
@@ -162,6 +185,7 @@ function addSetFormatCommand(dev, handleSetFormat) {
162
185
  * @returns {void}
163
186
  */
164
187
  function setupDevPathAndFormatCommands(dev, handleSetFormat) {
188
+ addSetEnvConfigCommand(dev);
165
189
  addSetHomeCommand(dev);
166
190
  addSetWorkCommand(dev);
167
191
  addPrintHomeWorkCommands(dev);
@@ -21,6 +21,11 @@ const {
21
21
  handleDevPin,
22
22
  handleDevDelete
23
23
  } = require('../commands/dev-cli-handlers');
24
+ const {
25
+ handlePlatformInstallCli,
26
+ handlePlatformUpdate,
27
+ handlePlatformInstall
28
+ } = require('../commands/dev-host-ops');
24
29
 
25
30
  /** Appended to `dev update --help` (examples + partial-update behaviour). */
26
31
  const DEV_UPDATE_HELP_AFTER = `
@@ -49,7 +54,7 @@ const DEV_GROUP_HELP_AFTER = `
49
54
  Categories:
50
55
  Local config show, set-id, set-scoped-resources, set-env-config, set-home, set-work, set-format, print-home, print-work
51
56
  Onboarding init (PIN onboarding), refresh (sync settings / renew cert)
52
- Remote admin list, add, update, pin, delete (need remote-server + client cert in ~/.aifabrix)
57
+ Remote admin list, add, update, pin, delete, platform-install, platform-update, platform-install-cli (need remote-server + client cert in ~/.aifabrix)
53
58
  Sync / stop down (Mutagen; optional --apps for containers)
54
59
 
55
60
  Use: aifabrix dev <command> --help
@@ -85,10 +90,31 @@ when their config already has remote-server + developer-id.
85
90
 
86
91
  /** Appended to `dev delete --help`. */
87
92
  const DEV_DELETE_HELP_AFTER = `
88
- Example:
93
+ Examples:
89
94
  $ aifabrix dev delete 02
95
+ $ aifabrix dev delete 02 -y
96
+ $ aifabrix dev delete 02 --db-only
97
+
98
+ Default removes the developer from the host (OS user, containers) and server database. Use --db-only for database-only removal.
90
99
  `;
91
100
 
101
+ /** Shared host-ops flags for platform commands and dev add provision. */
102
+ function addHostOpsSharedOptions(cmd) {
103
+ return cmd
104
+ .option('--check', 'Preflight only (no changes)')
105
+ .option('-y, --yes', 'Non-interactive (skip prompts)')
106
+ .option('--wait', 'Poll until job completes', true)
107
+ .option('--no-wait', 'Return after job is queued')
108
+ .option('--json', 'JSON output (includes job result when --wait)')
109
+ .option('--include <ids>', 'Comma-separated developer ids (batch)')
110
+ .option('--exclude <ids>', 'Comma-separated developer ids to skip')
111
+ .option('--builder-version <ver>', 'Builder CLI / platform bundle version')
112
+ .option('--admin-email <email>', 'Platform admin email (install / provision)')
113
+ .option('--admin-password <password>', 'Platform admin password (never logged)')
114
+ .option('--platform <mode>', 'Platform mode (e.g. single-traefik)')
115
+ .option('--installation <profile>', 'Installation profile (e.g. dev)');
116
+ }
117
+
92
118
  /** Appended to `dev down --help`. */
93
119
  const DEV_DOWN_HELP_AFTER = `
94
120
  Examples:
@@ -276,6 +302,15 @@ function setupDevListAddCommands(dev) {
276
302
  .requiredOption('--name <name>', 'Display name')
277
303
  .requiredOption('--email <email>', 'Email address')
278
304
  .option('--groups <items>', 'Comma-separated roles: admin, secret-manager, secret-reader, developer, docker', 'developer')
305
+ .option('--no-provision', 'Create user in database only (skip host provision job)')
306
+ .option('--builder-version <ver>', 'Builder CLI version for provision job')
307
+ .option('--admin-email <email>', 'Platform admin email for provision job')
308
+ .option('--admin-password <password>', 'Platform admin password for provision job')
309
+ .option('--platform <mode>', 'Platform mode for provision job')
310
+ .option('--installation <profile>', 'Installation profile for provision job')
311
+ .option('--skip-cert', 'Skip certificate step in provision job')
312
+ .option('--skip-platform', 'Skip platform install in provision job')
313
+ .option('--skip-cli', 'Skip Builder CLI install in provision job')
279
314
  .addHelpText('after', DEV_ADD_HELP_AFTER)
280
315
  .action(async(options) => {
281
316
  try {
@@ -287,6 +322,27 @@ function setupDevListAddCommands(dev) {
287
322
  });
288
323
  }
289
324
 
325
+ /**
326
+ * Register dev down command.
327
+ * @param {Command} dev - dev subcommand group
328
+ */
329
+ function setupDevDownCommand(dev) {
330
+ dev
331
+ .command('down')
332
+ .description('Stop Mutagen sync; --apps also stops app containers')
333
+ .addHelpText('after', DEV_DOWN_HELP_AFTER)
334
+ .option('--apps', 'Also stop running app containers for this developer')
335
+ .action(async(options) => {
336
+ try {
337
+ const { handleDevDown } = require('../commands/dev-down');
338
+ await handleDevDown(options);
339
+ } catch (error) {
340
+ handleCommandError(error, 'dev down');
341
+ process.exit(1);
342
+ }
343
+ });
344
+ }
345
+
290
346
  /**
291
347
  * Register dev update/pin/delete commands (remote only).
292
348
  * @param {Command} dev - dev subcommand group
@@ -325,31 +381,68 @@ function setupDevUpdatePinDeleteCommands(dev) {
325
381
 
326
382
  dev
327
383
  .command('delete <developerId>')
328
- .description('Remove developer from server (admin)')
384
+ .description('Remove developer from host and server (admin)')
385
+ .option('--db-only', 'Remove from server database only (legacy behaviour)')
386
+ .option('-y, --yes', 'Skip confirmation prompt')
329
387
  .addHelpText('after', DEV_DELETE_HELP_AFTER)
330
- .action(async(developerId) => {
388
+ .action(async(developerId, options) => {
331
389
  try {
332
- await handleDevDelete(developerId);
390
+ await handleDevDelete(developerId, options);
333
391
  } catch (error) {
334
392
  handleCommandError(error, 'dev delete');
335
393
  process.exit(1);
336
394
  }
337
395
  });
338
396
 
339
- dev
340
- .command('down')
341
- .description('Stop Mutagen sync; --apps also stops app containers')
342
- .addHelpText('after', DEV_DOWN_HELP_AFTER)
343
- .option('--apps', 'Also stop running app containers for this developer')
344
- .action(async(options) => {
397
+ setupDevDownCommand(dev);
398
+ }
399
+
400
+ /**
401
+ * Register platform lifecycle commands (host-ops).
402
+ * @param {Command} dev - dev subcommand group
403
+ */
404
+ function setupDevPlatformCommands(dev) {
405
+ addHostOpsSharedOptions(
406
+ dev
407
+ .command('platform-install-cli [developerId]')
408
+ .description('Install or upgrade Builder CLI on dev host(s) (admin)')
409
+ )
410
+ .option('--force', 'Reinstall even when CLI is present')
411
+ .action(async(developerId, options) => {
345
412
  try {
346
- const { handleDevDown } = require('../commands/dev-down');
347
- await handleDevDown(options);
413
+ await handlePlatformInstallCli(developerId, options);
348
414
  } catch (error) {
349
- handleCommandError(error, 'dev down');
415
+ handleCommandError(error, 'dev platform-install-cli');
350
416
  process.exit(1);
351
417
  }
352
418
  });
419
+
420
+ addHostOpsSharedOptions(
421
+ dev
422
+ .command('platform-update [developerId]')
423
+ .description('Refresh dev platform images (admin)')
424
+ ).action(async(developerId, options) => {
425
+ try {
426
+ await handlePlatformUpdate(developerId, options);
427
+ } catch (error) {
428
+ handleCommandError(error, 'dev platform-update');
429
+ process.exit(1);
430
+ }
431
+ });
432
+
433
+ addHostOpsSharedOptions(
434
+ dev
435
+ .command('platform-install <developerId>')
436
+ .description('Install dev platform for one developer (admin)')
437
+ .option('--force', 'Remove existing platform then reinstall')
438
+ ).action(async(developerId, options) => {
439
+ try {
440
+ await handlePlatformInstall(developerId, options);
441
+ } catch (error) {
442
+ handleCommandError(error, 'dev platform-install');
443
+ process.exit(1);
444
+ }
445
+ });
353
446
  }
354
447
 
355
448
  /**
@@ -359,6 +452,7 @@ function setupDevUpdatePinDeleteCommands(dev) {
359
452
  function setupDevUserCommands(dev) {
360
453
  setupDevListAddCommands(dev);
361
454
  setupDevUpdatePinDeleteCommands(dev);
455
+ setupDevPlatformCommands(dev);
362
456
  }
363
457
 
364
458
  /**
@@ -16,6 +16,7 @@ const {
16
16
  validateDevGroups,
17
17
  augmentDevUserGroupsServerError
18
18
  } = require('../utils/dev-user-groups');
19
+ const { handleProvisionDevUser, handleRemoveDevUser } = require('./dev-host-ops');
19
20
 
20
21
  const REMOTE_NOT_CONFIGURED_MSG = 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
21
22
 
@@ -26,6 +27,19 @@ const CERT_WIDTH = 10;
26
27
  const UNTIL_WIDTH = 22; // 2026-03-20T17:31:14
27
28
  const TABLE_SEPARATOR_LENGTH = 130;
28
29
 
30
+ /**
31
+ * Normalize numeric developer id for comparison (e.g. 3 → 03).
32
+ * @param {string} id - Developer ID
33
+ * @returns {string}
34
+ */
35
+ function normalizeDeveloperIdForCompare(id) {
36
+ const trimmed = String(id ?? '').trim();
37
+ if (!/^\d+$/.test(trimmed)) {
38
+ return trimmed;
39
+ }
40
+ return String(parseInt(trimmed, 10)).padStart(2, '0');
41
+ }
42
+
29
43
  /**
30
44
  * Format certificate validity end for display (e.g. 2026-03-20T17:31:14.000Z -> 2026-03-20T17:31:14).
31
45
  * @param {string} iso - ISO 8601 date string
@@ -91,6 +105,10 @@ async function handleDevAdd(options) {
91
105
  groups
92
106
  }, auth.serverCaPem || undefined);
93
107
  logger.log(formatSuccessLine(`Developer ${user.id} created. Use "aifabrix dev pin ${user.id}" to create a PIN for onboarding.`));
108
+ const noProvision = options.noProvision || options['no-provision'];
109
+ if (!noProvision) {
110
+ await handleProvisionDevUser(user.id, options);
111
+ }
94
112
  } catch (err) {
95
113
  augmentDevUserGroupsServerError(err, groups);
96
114
  throw err;
@@ -222,18 +240,61 @@ async function handleDevPin(developerId, options = {}) {
222
240
  });
223
241
  }
224
242
 
243
+ /**
244
+ * @param {string} question
245
+ * @returns {Promise<boolean>}
246
+ */
247
+ async function promptYesNo(question) {
248
+ if (!process.stdin.isTTY) {
249
+ return false;
250
+ }
251
+ const readline = require('readline');
252
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
253
+ return new Promise((resolve) => {
254
+ rl.question(`${question} [y/N] `, (answer) => {
255
+ rl.close();
256
+ resolve(/^y(es)?$/i.test(String(answer || '').trim()));
257
+ });
258
+ });
259
+ }
260
+
225
261
  /**
226
262
  * Handle dev delete – remove developer (remote only).
263
+ * Default: full host removal via host-ops. Use --db-only for API-only delete.
227
264
  * @param {string} developerId - Developer ID
265
+ * @param {Object} [options] - Commander options
228
266
  * @returns {Promise<void>}
229
267
  */
230
- async function handleDevDelete(developerId) {
268
+ async function handleDevDelete(developerId, options = {}) {
231
269
  const auth = await getRemoteDevAuth();
232
270
  if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
233
271
  const id = developerId;
234
272
  if (!id) throw new Error('Developer ID is required (positional argument or --developer-id).');
235
- await devApi.deleteUser(auth.serverUrl, auth.clientCertPem, id, auth.serverCaPem || undefined);
236
- logger.log(formatSuccessLine(`Developer ${id} removed.`));
273
+ const actorId = await config.getDeveloperId();
274
+ if (
275
+ actorId &&
276
+ normalizeDeveloperIdForCompare(actorId) === normalizeDeveloperIdForCompare(id)
277
+ ) {
278
+ throw new Error('You cannot delete your own developer account.');
279
+ }
280
+ const dbOnly = options.dbOnly || options['db-only'];
281
+ if (dbOnly) {
282
+ await devApi.deleteUser(auth.serverUrl, auth.clientCertPem, id, auth.serverCaPem || undefined);
283
+ logger.log(formatSuccessLine(`Developer ${id} removed from server database.`));
284
+ return;
285
+ }
286
+ const assumeYes = options.yes || options.y;
287
+ if (!assumeYes) {
288
+ const ok = await promptYesNo(
289
+ `Remove developer ${id} from host and server (containers, OS user, database)?`
290
+ );
291
+ if (!ok) {
292
+ logger.log(chalk.gray('Cancelled.'));
293
+ return;
294
+ }
295
+ }
296
+ await handleRemoveDevUser(id, { ...options, developerId: id });
297
+ logger.log(formatSuccessLine(`Developer ${id} removal queued and completed.`));
237
298
  }
238
299
 
239
300
  module.exports = {