@aifabrix/builder 2.40.2 → 2.42.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/docs-rules.mdc +30 -0
- package/README.md +7 -5
- package/integration/hubspot/README.md +8 -4
- package/integration/hubspot/application.json +54 -0
- package/integration/hubspot/create-hubspot.js +9 -136
- package/integration/hubspot/env.template +3 -4
- package/integration/hubspot/hubspot-datasource-company.json +343 -5
- package/integration/hubspot/hubspot-datasource-contact.json +413 -5
- package/integration/hubspot/hubspot-datasource-deal.json +341 -4
- package/integration/hubspot/hubspot-datasource-users.json +116 -0
- package/integration/hubspot/hubspot-deploy.json +1250 -108
- package/integration/hubspot/hubspot-system.json +15 -32
- package/integration/hubspot/test-dataplane-down-tests.js +17 -16
- package/integration/hubspot/test-dataplane-down.js +2 -2
- package/integration/hubspot/test.js +1 -1
- package/jest.config.manual.js +2 -1
- package/lib/api/credential.api.js +40 -0
- package/lib/api/dev.api.js +423 -0
- package/lib/api/external-test.api.js +111 -0
- package/lib/api/index.js +42 -19
- package/lib/api/pipeline.api.js +66 -120
- package/lib/api/types/credential.types.js +23 -0
- package/lib/api/types/dev.types.js +140 -0
- package/lib/api/types/pipeline.types.js +37 -0
- package/lib/api/wizard-platform.api.js +61 -0
- package/lib/api/wizard.api.js +34 -1
- package/lib/app/config.js +44 -11
- package/lib/app/down.js +2 -1
- package/lib/app/index.js +12 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/push.js +36 -12
- package/lib/app/readme.js +9 -6
- package/lib/app/run-env-compose.js +264 -0
- package/lib/app/run-helpers.js +121 -118
- package/lib/app/run.js +148 -28
- package/lib/app/show-display.js +1 -1
- package/lib/app/show.js +5 -2
- package/lib/build/index.js +11 -3
- package/lib/cli/setup-app.js +172 -15
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +206 -16
- package/lib/cli/setup-environment.js +16 -6
- package/lib/cli/setup-external-system.js +89 -24
- package/lib/cli/setup-infra.js +82 -15
- package/lib/cli/setup-secrets.js +52 -5
- package/lib/cli/setup-utility.js +129 -24
- package/lib/commands/app-install.js +172 -0
- package/lib/commands/app-shell.js +75 -0
- package/lib/commands/app-test.js +282 -0
- package/lib/commands/app.js +1 -1
- package/lib/commands/credential-env.js +162 -0
- package/lib/commands/credential-list.js +17 -22
- package/lib/commands/credential-push.js +96 -0
- package/lib/commands/datasource.js +77 -6
- package/lib/commands/dev-cli-handlers.js +141 -0
- package/lib/commands/dev-down.js +114 -0
- package/lib/commands/dev-init.js +347 -0
- package/lib/commands/repair-auth-config.js +99 -0
- package/lib/commands/repair-datasource-keys.js +208 -0
- package/lib/commands/repair-datasource.js +235 -0
- package/lib/commands/repair-env-template.js +348 -0
- package/lib/commands/repair-internal.js +85 -0
- package/lib/commands/repair-rbac.js +158 -0
- package/lib/commands/repair.js +507 -0
- package/lib/commands/secrets-list.js +118 -0
- package/lib/commands/secrets-remove.js +97 -0
- package/lib/commands/secrets-set.js +30 -17
- package/lib/commands/secrets-validate.js +50 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/commands/up-miso.js +0 -25
- package/lib/commands/upload.js +96 -40
- package/lib/commands/wizard-core-helpers.js +226 -4
- package/lib/commands/wizard-core.js +67 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +44 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +86 -64
- package/lib/core/admin-secrets.js +96 -0
- package/lib/core/config.js +7 -1
- package/lib/core/secrets-ensure.js +378 -0
- package/lib/core/secrets-env-write.js +157 -0
- package/lib/core/secrets.js +176 -89
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/field-reference-validator.js +91 -0
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/datasource/validate.js +21 -3
- package/lib/deployment/deployer.js +7 -5
- package/lib/deployment/environment-config.js +137 -0
- package/lib/deployment/environment.js +21 -98
- package/lib/deployment/push.js +32 -2
- package/lib/external-system/download.js +188 -203
- package/lib/external-system/generator.js +204 -56
- package/lib/external-system/test-auth.js +7 -3
- package/lib/external-system/test-execution.js +2 -1
- package/lib/external-system/test-system-level.js +73 -0
- package/lib/external-system/test.js +56 -19
- package/lib/generator/external-controller-manifest.js +29 -2
- package/lib/generator/external-schema-utils.js +1 -1
- package/lib/generator/external.js +10 -3
- package/lib/generator/index.js +177 -25
- package/lib/generator/split-readme.js +1 -0
- package/lib/generator/split-variables.js +7 -1
- package/lib/generator/split.js +194 -54
- package/lib/generator/wizard-prompts-secondary.js +294 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +88 -0
- package/lib/generator/wizard.js +155 -158
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/helpers.js +103 -20
- package/lib/infrastructure/index.js +98 -12
- package/lib/infrastructure/services.js +88 -22
- package/lib/schema/application-schema.json +32 -8
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +509 -411
- package/lib/schema/wizard-config.schema.json +16 -0
- package/lib/utils/api.js +41 -13
- package/lib/utils/app-register-auth.js +25 -3
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/cli-utils.js +20 -0
- package/lib/utils/compose-generator.js +77 -76
- package/lib/utils/compose-handlebars-helpers.js +54 -0
- package/lib/utils/compose-vector-helper.js +18 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/config-paths.js +127 -2
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +357 -0
- package/lib/utils/dataplane-pipeline-warning.js +28 -0
- package/lib/utils/deployment-validation-helpers.js +4 -4
- package/lib/utils/dev-ca-install.js +139 -0
- package/lib/utils/dev-cert-helper.js +122 -0
- package/lib/utils/device-code-helpers.js +224 -0
- package/lib/utils/device-code.js +37 -336
- package/lib/utils/docker-build.js +40 -8
- package/lib/utils/env-copy.js +103 -13
- package/lib/utils/env-map.js +35 -5
- package/lib/utils/env-template.js +6 -5
- package/lib/utils/error-formatters/http-status-errors.js +20 -2
- package/lib/utils/error-formatters/permission-errors.js +0 -1
- package/lib/utils/error-formatters/validation-errors.js +0 -1
- package/lib/utils/external-readme.js +56 -29
- package/lib/utils/external-system-display.js +59 -1
- package/lib/utils/external-system-test-helpers.js +21 -8
- package/lib/utils/external-system-validators.js +3 -0
- package/lib/utils/file-upload.js +20 -50
- package/lib/utils/help-builder.js +16 -2
- package/lib/utils/infra-status.js +80 -45
- package/lib/utils/local-secrets.js +7 -52
- package/lib/utils/mutagen-install.js +195 -0
- package/lib/utils/mutagen.js +146 -0
- package/lib/utils/paths.js +128 -37
- package/lib/utils/port-resolver.js +28 -16
- package/lib/utils/remote-dev-auth.js +38 -0
- package/lib/utils/remote-docker-env.js +43 -0
- package/lib/utils/remote-secrets-loader.js +60 -0
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +114 -6
- package/lib/utils/secrets-helpers.js +108 -114
- package/lib/utils/secrets-path.js +2 -2
- package/lib/utils/secrets-utils.js +52 -1
- package/lib/utils/secrets-validation.js +84 -0
- package/lib/utils/ssh-key-helper.js +116 -0
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager-messages.js +90 -0
- package/lib/utils/token-manager.js +29 -36
- package/lib/utils/variable-transformer.js +3 -3
- package/lib/validation/env-template-auth.js +157 -0
- package/lib/validation/env-template-kv.js +41 -0
- package/lib/validation/external-manifest-validator.js +25 -0
- package/lib/validation/external-system-auth-rules.js +86 -0
- package/lib/validation/validate-batch.js +149 -0
- package/lib/validation/validate-datasource-keys-api.js +33 -0
- package/lib/validation/validate-display.js +94 -16
- package/lib/validation/validate.js +25 -12
- package/lib/validation/validator.js +72 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +8 -3
- package/scripts/install-local.js +34 -15
- package/templates/README.md +0 -1
- package/templates/applications/README.md.hbs +4 -4
- package/templates/applications/dataplane/application.yaml +6 -5
- package/templates/applications/dataplane/env.template +15 -10
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/keycloak/env.template +2 -0
- package/templates/applications/miso-controller/application.yaml +1 -0
- package/templates/applications/miso-controller/env.template +12 -10
- package/templates/external-system/README.md.hbs +65 -25
- package/templates/external-system/deploy.js.hbs +4 -2
- package/templates/external-system/external-datasource.yaml.hbs +217 -0
- package/templates/external-system/external-system.json.hbs +1 -18
- package/templates/infra/compose.yaml.hbs +6 -0
- package/templates/python/docker-compose.hbs +49 -23
- package/templates/typescript/docker-compose.hbs +48 -22
- package/integration/hubspot/application.yaml +0 -37
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI Fabrix Builder - Datasource Commands
|
|
3
3
|
*
|
|
4
4
|
* Handles datasource validation, listing, comparison, and deployment
|
|
5
|
-
* Commands: datasource validate, datasource list, datasource diff, datasource
|
|
5
|
+
* Commands: datasource validate, datasource list, datasource diff, datasource upload
|
|
6
6
|
*
|
|
7
7
|
* @fileoverview Datasource management commands for AI Fabrix Builder
|
|
8
8
|
* @author AI Fabrix Team
|
|
@@ -15,6 +15,9 @@ const { validateDatasourceFile } = require('../datasource/validate');
|
|
|
15
15
|
const { listDatasources } = require('../datasource/list');
|
|
16
16
|
const { compareDatasources } = require('../datasource/diff');
|
|
17
17
|
const { deployDatasource } = require('../datasource/deploy');
|
|
18
|
+
const { runDatasourceTestIntegration } = require('../datasource/test-integration');
|
|
19
|
+
const { runDatasourceTestE2E } = require('../datasource/test-e2e');
|
|
20
|
+
const { displayIntegrationTestResults, displayE2EResults } = require('../utils/external-system-display');
|
|
18
21
|
|
|
19
22
|
function setupDatasourceValidateCommand(datasource) {
|
|
20
23
|
datasource.command('validate <file>')
|
|
@@ -62,14 +65,80 @@ function setupDatasourceDiffCommand(datasource) {
|
|
|
62
65
|
});
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
function
|
|
66
|
-
datasource.command('
|
|
67
|
-
.description('
|
|
68
|
+
function setupDatasourceUploadCommand(datasource) {
|
|
69
|
+
datasource.command('upload <myapp> <file>')
|
|
70
|
+
.description('Upload datasource to dataplane')
|
|
68
71
|
.action(async(myapp, file, options) => {
|
|
69
72
|
try {
|
|
70
73
|
await deployDatasource(myapp, file, options);
|
|
71
74
|
} catch (error) {
|
|
72
|
-
logger.error(chalk.red('❌
|
|
75
|
+
logger.error(chalk.red('❌ Upload failed:'), error.message);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function setupDatasourceTestIntegrationCommand(datasource) {
|
|
82
|
+
datasource.command('test-integration <datasourceKey>')
|
|
83
|
+
.description('Run integration (config) test for one datasource via dataplane pipeline')
|
|
84
|
+
.option('-a, --app <appKey>', 'App key (default: resolve from cwd if inside integration/<appKey>/)')
|
|
85
|
+
.option('-p, --payload <file>', 'Path to custom test payload file')
|
|
86
|
+
.option('-e, --env <env>', 'Environment: dev, tst, or pro')
|
|
87
|
+
.option('--debug', 'Include debug output and write log to integration/<appKey>/logs/')
|
|
88
|
+
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
89
|
+
.action(async(datasourceKey, options) => {
|
|
90
|
+
try {
|
|
91
|
+
const result = await runDatasourceTestIntegration(datasourceKey, {
|
|
92
|
+
app: options.app,
|
|
93
|
+
payload: options.payload,
|
|
94
|
+
environment: options.env,
|
|
95
|
+
debug: options.debug,
|
|
96
|
+
timeout: options.timeout
|
|
97
|
+
});
|
|
98
|
+
displayIntegrationTestResults({
|
|
99
|
+
systemKey: result.systemKey || 'unknown',
|
|
100
|
+
datasourceResults: [result],
|
|
101
|
+
success: result.success
|
|
102
|
+
}, options.verbose);
|
|
103
|
+
if (!result.success) process.exit(1);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(chalk.red('❌ Integration test failed:'), error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setupDatasourceTestE2ECommand(datasource) {
|
|
112
|
+
datasource.command('test-e2e <datasourceKey>')
|
|
113
|
+
.description('Run E2E test for one datasource (config, credential, sync, data, CIP) via dataplane')
|
|
114
|
+
.option('-a, --app <appKey>', 'App key (default: resolve from cwd if inside integration/<appKey>/)')
|
|
115
|
+
.option('-e, --env <env>', 'Environment: dev, tst, or pro')
|
|
116
|
+
.option('-v, --verbose', 'Show detailed step output and poll progress')
|
|
117
|
+
.option('--debug', 'Include debug output and write log to integration/<appKey>/logs/')
|
|
118
|
+
.option('--test-crud', 'Enable CRUD lifecycle test (body testCrud: true)')
|
|
119
|
+
.option('--record-id <id>', 'Record ID for test (body recordId)')
|
|
120
|
+
.option('--no-cleanup', 'Disable cleanup after test (body cleanup: false)')
|
|
121
|
+
.option('--primary-key-value <value|@path>', 'Primary key value or path to JSON file (e.g. @pk.json) for body primaryKeyValue')
|
|
122
|
+
.option('--no-async', 'Use sync mode (no polling); single POST, no asyncRun')
|
|
123
|
+
.action(async(datasourceKey, options) => {
|
|
124
|
+
try {
|
|
125
|
+
const data = await runDatasourceTestE2E(datasourceKey, {
|
|
126
|
+
app: options.app,
|
|
127
|
+
environment: options.env,
|
|
128
|
+
debug: options.debug,
|
|
129
|
+
verbose: options.verbose,
|
|
130
|
+
async: options.async !== false,
|
|
131
|
+
testCrud: options.testCrud,
|
|
132
|
+
recordId: options.recordId,
|
|
133
|
+
cleanup: options.cleanup,
|
|
134
|
+
primaryKeyValue: options.primaryKeyValue
|
|
135
|
+
});
|
|
136
|
+
displayE2EResults(data, options.verbose);
|
|
137
|
+
const steps = data.steps || data.completedActions || [];
|
|
138
|
+
const failed = data.success === false || steps.some(s => s.success === false || s.error);
|
|
139
|
+
if (failed) process.exit(1);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error(chalk.red('❌ E2E test failed:'), error.message);
|
|
73
142
|
process.exit(1);
|
|
74
143
|
}
|
|
75
144
|
});
|
|
@@ -84,7 +153,9 @@ function setupDatasourceCommands(program) {
|
|
|
84
153
|
setupDatasourceValidateCommand(datasource);
|
|
85
154
|
setupDatasourceListCommand(datasource);
|
|
86
155
|
setupDatasourceDiffCommand(datasource);
|
|
87
|
-
|
|
156
|
+
setupDatasourceUploadCommand(datasource);
|
|
157
|
+
setupDatasourceTestIntegrationCommand(datasource);
|
|
158
|
+
setupDatasourceTestE2ECommand(datasource);
|
|
88
159
|
}
|
|
89
160
|
|
|
90
161
|
module.exports = { setupDatasourceCommands };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI action handlers for dev list/add/update/pin/delete (remote Builder Server)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const config = require('../core/config');
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const devApi = require('../api/dev.api');
|
|
11
|
+
const { getRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
12
|
+
|
|
13
|
+
const REMOTE_NOT_CONFIGURED_MSG = 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
|
|
14
|
+
|
|
15
|
+
const ID_WIDTH = 8;
|
|
16
|
+
const NAME_WIDTH = 25;
|
|
17
|
+
const EMAIL_WIDTH = 30;
|
|
18
|
+
const CERT_WIDTH = 10;
|
|
19
|
+
const UNTIL_WIDTH = 22; // 2026-03-20T17:31:14
|
|
20
|
+
const TABLE_SEPARATOR_LENGTH = 130;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format certificate validity end for display (e.g. 2026-03-20T17:31:14.000Z -> 2026-03-20T17:31:14).
|
|
24
|
+
* @param {string} iso - ISO 8601 date string
|
|
25
|
+
* @returns {string} Truncated datetime without milliseconds and Z
|
|
26
|
+
*/
|
|
27
|
+
function formatCertUntil(iso) {
|
|
28
|
+
if (!iso || typeof iso !== 'string') return '?';
|
|
29
|
+
return iso.replace(/\.\d{3}Z?$/i, '').trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handle dev list – list developer users (remote only). Table format, sorted by name.
|
|
34
|
+
* @returns {Promise<void>}
|
|
35
|
+
*/
|
|
36
|
+
async function handleDevList() {
|
|
37
|
+
const auth = await getRemoteDevAuth();
|
|
38
|
+
if (!auth) {
|
|
39
|
+
logger.log(chalk.yellow(REMOTE_NOT_CONFIGURED_MSG));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const users = await devApi.listUsers(auth.serverUrl, auth.clientCertPem);
|
|
43
|
+
if (users.length === 0) {
|
|
44
|
+
logger.log(chalk.gray('No developers registered.'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
logger.log(chalk.bold('\n📋 Developers:\n'));
|
|
48
|
+
logger.log(chalk.gray('ID'.padEnd(ID_WIDTH) + 'Name'.padEnd(NAME_WIDTH) + 'Email'.padEnd(EMAIL_WIDTH) + 'Cert'.padEnd(CERT_WIDTH) + 'Until'.padEnd(UNTIL_WIDTH) + 'Groups'));
|
|
49
|
+
logger.log(chalk.gray('-'.repeat(TABLE_SEPARATOR_LENGTH)));
|
|
50
|
+
const sorted = [...users].sort((a, b) => (a.name || '').localeCompare(b.name || '', undefined, { sensitivity: 'base' }));
|
|
51
|
+
sorted.forEach(u => {
|
|
52
|
+
const certLabel = u.certificateIssued ? 'yes' : 'no cert';
|
|
53
|
+
const untilRaw = u.certificateIssued && u.certificateValidNotAfter ? u.certificateValidNotAfter : null;
|
|
54
|
+
const untilStr = untilRaw ? formatCertUntil(untilRaw) : '-';
|
|
55
|
+
const id = String(u.id ?? '').padEnd(ID_WIDTH);
|
|
56
|
+
const name = (u.name || 'N/A').padEnd(NAME_WIDTH);
|
|
57
|
+
const email = (u.email || 'N/A').padEnd(EMAIL_WIDTH);
|
|
58
|
+
const cert = certLabel.padEnd(CERT_WIDTH);
|
|
59
|
+
const until = untilStr.padEnd(UNTIL_WIDTH);
|
|
60
|
+
const groups = (u.groups || []).join(', ');
|
|
61
|
+
logger.log(`${id}${name}${email}${cert}${until}${groups}`);
|
|
62
|
+
});
|
|
63
|
+
logger.log('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handle dev add – create developer (remote only).
|
|
68
|
+
* @param {Object} options - Commander options (developerId, name, email, groups)
|
|
69
|
+
* @returns {Promise<void>}
|
|
70
|
+
*/
|
|
71
|
+
async function handleDevAdd(options) {
|
|
72
|
+
const auth = await getRemoteDevAuth();
|
|
73
|
+
if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
|
|
74
|
+
const groups = (options.groups || 'developer').split(',').map(s => s.trim()).filter(Boolean);
|
|
75
|
+
const user = await devApi.createUser(auth.serverUrl, auth.clientCertPem, {
|
|
76
|
+
developerId: options.developerId,
|
|
77
|
+
name: options.name,
|
|
78
|
+
email: options.email,
|
|
79
|
+
groups: groups.length ? groups : ['developer']
|
|
80
|
+
});
|
|
81
|
+
logger.log(chalk.green(`✓ Developer ${user.id} created. Use "aifabrix dev pin ${user.id}" to create a PIN for onboarding.`));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handle dev update – update developer (remote only).
|
|
86
|
+
* @param {string} developerId - Developer ID
|
|
87
|
+
* @param {Object} options - Commander options (name, email, groups)
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async function handleDevUpdate(developerId, options) {
|
|
91
|
+
const auth = await getRemoteDevAuth();
|
|
92
|
+
if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
|
|
93
|
+
const id = options.developerId || options['developer-id'] || developerId;
|
|
94
|
+
if (!id) throw new Error('Developer ID is required (--developer-id or positional argument).');
|
|
95
|
+
const body = {};
|
|
96
|
+
if (options.name) body.name = options.name;
|
|
97
|
+
if (options.email) body.email = options.email;
|
|
98
|
+
if (options.groups) body.groups = options.groups.split(',').map(s => s.trim()).filter(Boolean);
|
|
99
|
+
if (Object.keys(body).length === 0) {
|
|
100
|
+
throw new Error('Provide at least one of --name, --email, --groups');
|
|
101
|
+
}
|
|
102
|
+
await devApi.updateUser(auth.serverUrl, auth.clientCertPem, id, body);
|
|
103
|
+
logger.log(chalk.green(`✓ Developer ${id} updated.`));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Handle dev pin – create/regenerate PIN (remote only).
|
|
108
|
+
* @param {string} [developerId] - Developer ID (optional; uses config if omitted)
|
|
109
|
+
* @returns {Promise<void>}
|
|
110
|
+
*/
|
|
111
|
+
async function handleDevPin(developerId) {
|
|
112
|
+
const auth = await getRemoteDevAuth();
|
|
113
|
+
if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
|
|
114
|
+
const id = developerId || await config.getDeveloperId();
|
|
115
|
+
if (!id) throw new Error('developerId is required (argument or set developer-id in config)');
|
|
116
|
+
const res = await devApi.createPin(auth.serverUrl, auth.clientCertPem, id);
|
|
117
|
+
logger.log(chalk.green(`✓ PIN created for ${id}, expires ${res.expiresAt}.`));
|
|
118
|
+
logger.log(chalk.yellow(` Give this PIN once to the developer for: aifabrix dev init --developer-id ${id} --server ${auth.serverUrl} --pin ${res.pin}`));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Handle dev delete – remove developer (remote only).
|
|
123
|
+
* @param {string} developerId - Developer ID
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async function handleDevDelete(developerId) {
|
|
127
|
+
const auth = await getRemoteDevAuth();
|
|
128
|
+
if (!auth) throw new Error(REMOTE_NOT_CONFIGURED_MSG);
|
|
129
|
+
const id = developerId;
|
|
130
|
+
if (!id) throw new Error('Developer ID is required (positional argument or --developer-id).');
|
|
131
|
+
await devApi.deleteUser(auth.serverUrl, auth.clientCertPem, id);
|
|
132
|
+
logger.log(chalk.green(`✓ Developer ${id} removed.`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
handleDevList,
|
|
137
|
+
handleDevAdd,
|
|
138
|
+
handleDevUpdate,
|
|
139
|
+
handleDevPin,
|
|
140
|
+
handleDevDelete
|
|
141
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev down – stop Mutagen sync sessions and optionally app containers for this developer.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Dev down command (plan 65: stop sync sessions; optional stop apps)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { exec } = require('child_process');
|
|
10
|
+
const { promisify } = require('util');
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const config = require('../core/config');
|
|
14
|
+
const appLib = require('../app');
|
|
15
|
+
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Stop Mutagen sync sessions for this developer (session names aifabrix-<dev-id>-*).
|
|
20
|
+
* When Mutagen is not available or no sessions exist, no-op.
|
|
21
|
+
* @param {string} developerId - Developer ID
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
async function stopMutagenSessions(developerId) {
|
|
25
|
+
const { getMutagenPath } = require('../utils/mutagen');
|
|
26
|
+
const mutagenPath = await getMutagenPath();
|
|
27
|
+
if (!mutagenPath) {
|
|
28
|
+
logger.log(chalk.gray('Mutagen not installed; no sync sessions to stop.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await execAsync(`"${mutagenPath}" sync list --template '{{.Name}}'`, {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
timeout: 5000
|
|
35
|
+
});
|
|
36
|
+
const sessions = (stdout || '').trim().split('\n').filter(Boolean);
|
|
37
|
+
const prefix = `aifabrix-${developerId}-`;
|
|
38
|
+
const toTerminate = sessions.filter(name => name.startsWith(prefix));
|
|
39
|
+
for (const name of toTerminate) {
|
|
40
|
+
await execAsync(`"${mutagenPath}" sync terminate "${name}"`, { timeout: 5000 });
|
|
41
|
+
logger.log(chalk.green(` ✓ Stopped sync session: ${name}`));
|
|
42
|
+
}
|
|
43
|
+
if (toTerminate.length === 0 && sessions.length > 0) {
|
|
44
|
+
logger.log(chalk.gray('No sync sessions for this developer.'));
|
|
45
|
+
} else if (toTerminate.length === 0) {
|
|
46
|
+
logger.log(chalk.gray('No Mutagen sync sessions to stop.'));
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.log(chalk.gray('No Mutagen sync sessions to stop.'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const INFRA_SUFFIXES = ['-postgres', '-redis', '-pgadmin', '-redis-commander', '-traefik', '-db-init'];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* List running app container names for this developer (excludes infra containers).
|
|
57
|
+
* @param {string} developerId - Developer ID
|
|
58
|
+
* @returns {Promise<string[]>} Container names (e.g. aifabrix-dev1-myapp)
|
|
59
|
+
*/
|
|
60
|
+
async function listAppContainersForDeveloper(developerId) {
|
|
61
|
+
const idNum = parseInt(developerId, 10);
|
|
62
|
+
const filter = idNum === 0 ? 'aifabrix-' : `aifabrix-dev${developerId}-`;
|
|
63
|
+
const { stdout } = await execAsync(
|
|
64
|
+
`docker ps --filter "name=${filter}" --format "{{.Names}}"`,
|
|
65
|
+
{ encoding: 'utf8' }
|
|
66
|
+
);
|
|
67
|
+
const names = (stdout || '').trim().split('\n').filter(Boolean);
|
|
68
|
+
return names.filter(n => !INFRA_SUFFIXES.some(s => n.endsWith(s)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Extract app name from container name (aifabrix-dev1-myapp -> myapp, aifabrix-myapp -> myapp).
|
|
73
|
+
* @param {string} containerName - Container name
|
|
74
|
+
* @param {string} developerId - Developer ID
|
|
75
|
+
* @returns {string} App name
|
|
76
|
+
*/
|
|
77
|
+
function appNameFromContainer(containerName, developerId) {
|
|
78
|
+
const idNum = parseInt(developerId, 10);
|
|
79
|
+
const prefix = idNum === 0 ? 'aifabrix-' : `aifabrix-dev${developerId}-`;
|
|
80
|
+
return containerName.startsWith(prefix) ? containerName.slice(prefix.length) : containerName;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Handle dev down: stop Mutagen sessions; optionally stop app containers.
|
|
85
|
+
* @param {Object} options - { apps: boolean }
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async function handleDevDown(options = {}) {
|
|
89
|
+
const developerId = await config.getDeveloperId();
|
|
90
|
+
logger.log(chalk.blue('\nStopping dev resources for developer ' + developerId + '...\n'));
|
|
91
|
+
|
|
92
|
+
await stopMutagenSessions(developerId);
|
|
93
|
+
|
|
94
|
+
if (options.apps) {
|
|
95
|
+
const containers = await listAppContainersForDeveloper(developerId);
|
|
96
|
+
for (const containerName of containers) {
|
|
97
|
+
const appName = appNameFromContainer(containerName, developerId);
|
|
98
|
+
if (!appName) continue;
|
|
99
|
+
try {
|
|
100
|
+
await appLib.downApp(appName, {});
|
|
101
|
+
logger.log(chalk.green(` ✓ Stopped app: ${appName}`));
|
|
102
|
+
} catch (err) {
|
|
103
|
+
logger.log(chalk.yellow(` ⚠ Could not stop ${appName}: ${err.message}`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (containers.length === 0) {
|
|
107
|
+
logger.log(chalk.gray('No running app containers for this developer.'));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
logger.log(chalk.green('\n✓ dev down complete.\n'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { handleDevDown };
|