@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.
- package/.cursor/process/README.md +9 -0
- package/.cursor/skills/README.md +14 -0
- package/lib/api/dev.api.js +1 -0
- package/lib/api/host-ops.api.js +69 -0
- package/lib/cli/infra-guided-auth.js +8 -6
- package/lib/cli/infra-guided.js +11 -2
- package/lib/cli/setup-dev-path-commands.js +24 -0
- package/lib/cli/setup-dev.js +108 -14
- package/lib/commands/dev-cli-handlers.js +64 -3
- package/lib/commands/dev-host-ops.js +239 -0
- package/lib/commands/teardown.js +3 -5
- package/lib/commands/up-common.js +8 -11
- package/lib/commands/up-dataplane.js +81 -13
- package/lib/commands/up-miso.js +6 -2
- package/lib/schema/external-system.schema.json +114 -2
- package/lib/utils/health-check-poll-ui.js +63 -28
- package/lib/utils/health-check.js +50 -21
- package/lib/utils/paths.js +26 -36
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +2 -1
- package/templates/applications/miso-controller/env.template +2 -0
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +0 -17
- 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/`.
|
package/lib/api/dev.api.js
CHANGED
|
@@ -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
|
|
package/lib/cli/infra-guided.js
CHANGED
|
@@ -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', {
|
|
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', {
|
|
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);
|
package/lib/cli/setup-dev.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
347
|
-
await handleDevDown(options);
|
|
413
|
+
await handlePlatformInstallCli(developerId, options);
|
|
348
414
|
} catch (error) {
|
|
349
|
-
handleCommandError(error, 'dev
|
|
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
|
-
|
|
236
|
-
|
|
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 = {
|