@aifabrix/builder 2.57.0 → 2.59.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.
Files changed (106) hide show
  1. package/lib/agent-kit/codex-app-server.js +44 -0
  2. package/lib/agent-kit/init.js +4 -17
  3. package/lib/agent-kit/session-report.js +15 -10
  4. package/lib/agent-kit/setup-auth.js +131 -0
  5. package/lib/agent-kit/setup-context.js +118 -0
  6. package/lib/agent-kit/setup-github.js +70 -0
  7. package/lib/agent-kit/setup-managed.js +79 -0
  8. package/lib/agent-kit/setup-process.js +42 -0
  9. package/lib/agent-kit/setup-session.js +53 -0
  10. package/lib/agent-kit/setup-state.js +52 -0
  11. package/lib/agent-kit/setup-workspace.js +113 -0
  12. package/lib/agent-kit/setup.js +84 -0
  13. package/lib/agent-kit/start.js +4 -3
  14. package/lib/agent-kit/tmux-sessions.js +20 -3
  15. package/lib/api/dev.api.js +1 -1
  16. package/lib/api/miso-health.api.js +26 -0
  17. package/lib/api/types/dev.types.js +3 -3
  18. package/lib/app/deploy.js +0 -102
  19. package/lib/app/push.js +98 -41
  20. package/lib/build/standard-docker-build.js +13 -11
  21. package/lib/cli/index.js +4 -0
  22. package/lib/cli/setup-app.js +16 -5
  23. package/lib/cli/setup-dev-path-commands.js +53 -2
  24. package/lib/cli/setup-dev.js +7 -4
  25. package/lib/cli/setup-environment.js +17 -4
  26. package/lib/cli/setup-help.js +86 -0
  27. package/lib/cli/setup-onboarding.js +37 -0
  28. package/lib/cli/setup-utility-repair.js +7 -5
  29. package/lib/commands/agent-kit-help.js +21 -7
  30. package/lib/commands/agent-kit-sessions.js +126 -0
  31. package/lib/commands/agent-kit-setup.js +44 -0
  32. package/lib/commands/agent-kit.js +5 -79
  33. package/lib/commands/dev-init.js +21 -23
  34. package/lib/commands/dev-refresh.js +2 -2
  35. package/lib/commands/dev-registry.js +155 -0
  36. package/lib/commands/dev-show-display.js +10 -2
  37. package/lib/commands/governance-verify-external.js +12 -5
  38. package/lib/commands/repair-datasource.js +9 -9
  39. package/lib/commands/repair.js +45 -8
  40. package/lib/commands/setup-image-refresh.js +26 -0
  41. package/lib/commands/upload-qualification-client.js +56 -11
  42. package/lib/commands/upload-qualification-poll.js +51 -22
  43. package/lib/commands/wizard-dataplane.js +16 -52
  44. package/lib/core/config-attach-extensions.js +4 -1
  45. package/lib/datasource/datasource-validate-summary.js +8 -5
  46. package/lib/datasource/sync-defaults.js +53 -0
  47. package/lib/deployment/installation/azure-infra-stage.js +50 -9
  48. package/lib/deployment/installation/azure-preflight.js +63 -7
  49. package/lib/deployment/installation/azure-readiness.js +59 -7
  50. package/lib/deployment/installation/environment-auth.js +60 -20
  51. package/lib/deployment/installation/environment-stage.js +9 -5
  52. package/lib/deployment/installation/infra-catalog.js +57 -5
  53. package/lib/deployment/installation/infra-manifest.js +22 -2
  54. package/lib/deployment/installation/infra-projection.js +14 -3
  55. package/lib/deployment/installation/registry-auth-mode.js +121 -0
  56. package/lib/deployment/installation/stage-input.js +21 -6
  57. package/lib/deployment/marketplace-environment.js +26 -1
  58. package/lib/generator/builders.js +8 -9
  59. package/lib/generator/index.js +23 -12
  60. package/lib/integration-definition/apply.js +2 -2
  61. package/lib/internal/fs-real-sync.js +4 -1
  62. package/lib/programmatic/bearer-auth.js +3 -62
  63. package/lib/programmatic/builder-api-startup.js +51 -0
  64. package/lib/programmatic/builder-miso-runtime.js +167 -0
  65. package/lib/programmatic/openapi-descriptions.js +3 -3
  66. package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
  67. package/lib/programmatic/openapi-spec.js +2 -1
  68. package/lib/programmatic/route-handler-map.js +3 -2
  69. package/lib/schema/environment-deploy-request.schema.json +1 -1
  70. package/lib/schema/external-datasource.schema.json +20 -5
  71. package/lib/schema/infra-parameter.schema.json +1 -1
  72. package/lib/schema/infra.parameter.yaml +2 -1
  73. package/lib/schema/infrastructure-schema.json +200 -3
  74. package/lib/schema/installation-environment.schema.json +1 -1
  75. package/lib/schema/wizard-config.schema.json +1 -1
  76. package/lib/utils/app-register-config.js +2 -4
  77. package/lib/utils/build-resolve-image.js +2 -21
  78. package/lib/utils/cli-utils.js +8 -1
  79. package/lib/utils/config-paths.js +24 -29
  80. package/lib/utils/config-registry-preference.js +113 -0
  81. package/lib/utils/dev-init-ssh-merge.js +5 -4
  82. package/lib/utils/dev-user-groups.js +2 -1
  83. package/lib/utils/docker-build.js +53 -19
  84. package/lib/utils/docker-daemon-tls-ca.js +2 -2
  85. package/lib/utils/env-map.js +23 -6
  86. package/lib/utils/error-formatters/validation-errors.js +25 -5
  87. package/lib/utils/help-builder.js +4 -3
  88. package/lib/utils/image-tags.js +88 -0
  89. package/lib/utils/promote-dev-docker-image.js +6 -0
  90. package/lib/utils/registry-credentials.js +236 -0
  91. package/lib/utils/resolve-docker-image-ref.js +78 -9
  92. package/lib/utils/secrets-helpers.js +9 -1
  93. package/lib/utils/token-manager.js +29 -36
  94. package/lib/utils/variable-transformer.js +2 -4
  95. package/package.json +4 -2
  96. package/templates/agent-kit/instructions/AGENTKIT.md +39 -2
  97. package/templates/agent-kit/instructions/root.AGENTS.md +40 -1
  98. package/templates/agent-kit/skills/shared/hosts.md +23 -5
  99. package/templates/agent-kit/workspace/README.md +14 -1
  100. package/templates/applications/builder-api/application.yaml +1 -1
  101. package/templates/applications/dataplane/application.yaml +1 -1
  102. package/templates/applications/dataplane/env.template +1 -1
  103. package/templates/applications/miso-controller/application.yaml +1 -1
  104. package/templates/applications/miso-controller/env.template +1 -1
  105. package/templates/external-system/external-datasource.yaml.hbs +7 -6
  106. package/templates/marketplace/main.json +440 -82
@@ -0,0 +1,52 @@
1
+ /** Machine-local onboarding progress, outside the repository and without credentials. */
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const fs = require('../internal/fs-real-sync');
6
+ const { randomUUID } = require('crypto');
7
+ const { assertDirectory } = require('./setup-context');
8
+
9
+ function readState(file) {
10
+ if (!fs.existsSync(file)) return null;
11
+ if (!fs.lstatSync(file).isFile()) throw new Error('Onboarding state must be a regular file.');
12
+ try {
13
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
14
+ } catch {
15
+ throw new Error('Onboarding state is invalid; preserve the workspace and inspect its state file.');
16
+ }
17
+ }
18
+
19
+ function acquireWorkspace(context) {
20
+ const dir = path.join(context.root, '.aifabrix-onboarding');
21
+ assertDirectory(dir);
22
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
23
+ const file = path.join(dir, `${context.name}.json`);
24
+ const lock = `${file}.lock`;
25
+ let descriptor;
26
+ try {
27
+ descriptor = fs.openSync(lock, 'wx', 0o600);
28
+ } catch {
29
+ throw new Error(`Workspace setup is locked. If no setup is running, remove the stale lock: ${lock}`);
30
+ }
31
+ fs.closeSync(descriptor);
32
+ const release = () => fs.unlinkSync(lock);
33
+ try {
34
+ const state = readState(file);
35
+ if (state && (state.controller !== context.controller || state.fullName !== context.fullName || state.target !== context.target)) {
36
+ throw new Error('This workspace belongs to a different Controller or repository. Use a different AI Fabrix work directory.');
37
+ }
38
+ return { file, release, state: state || { controller: context.controller, fullName: context.fullName, target: context.target } };
39
+ } catch (error) {
40
+ release();
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ function saveProgress(progress, patch) {
46
+ Object.assign(progress.state, patch);
47
+ const temporary = `${progress.file}.${randomUUID()}.tmp`;
48
+ fs.writeFileSync(temporary, `${JSON.stringify(progress.state, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
49
+ fs.renameSync(temporary, progress.file);
50
+ }
51
+
52
+ module.exports = { acquireWorkspace, saveProgress };
@@ -0,0 +1,113 @@
1
+ /** Local workspace preparation and optional GitHub clone/create with safe resumability. */
2
+ 'use strict';
3
+
4
+ const fs = require('../internal/fs-real-sync');
5
+ const { runInit } = require('./init');
6
+ const { runInstall, runUpdate } = require('./run');
7
+ const { resolveGitRoot } = require('./repo-guard');
8
+ const { ensureInitialCommit } = require('./scm/git');
9
+ const { capture, checked } = require('./setup-process');
10
+ const github = require('./setup-github');
11
+ const { saveProgress } = require('./setup-state');
12
+
13
+ function validateExisting(context, progress, options) {
14
+ if (progress.state.initializing || progress.state.cloning) {
15
+ throw new Error('Workspace initialization or clone was interrupted. Inspect the preserved folder and onboarding state before retrying.');
16
+ }
17
+ if (!fs.existsSync(context.target) || fs.readdirSync(context.target).length === 0) return false;
18
+ if (resolveGitRoot(context.target) !== context.target) throw new Error('Workspace must be its own Git repository.');
19
+ if (context.fullName) {
20
+ const origin = github.originIdentity(context.target, options);
21
+ if (origin || !progress.state.initialized) github.assertOrigin(context, options);
22
+ } else if (!progress.state.initialized || github.originIdentity(context.target, options)) {
23
+ throw new Error('Local workspace is not an existing local-only onboarding workspace. No files were changed.');
24
+ }
25
+ return true;
26
+ }
27
+
28
+ function checkKit(result) {
29
+ if (result.conflicts && result.conflicts.length) {
30
+ throw new Error('Agent Kit files have local conflicts. Resolve them with agent-kit status/update before retrying setup.');
31
+ }
32
+ }
33
+
34
+ function initializeLocal(context, progress, options) {
35
+ // Persist the operation first so an interrupted partial init cannot be adopted on retry.
36
+ saveProgress(progress, { initializing: true });
37
+ runInit({ nameOrPath: context.target, host: context.code, noRemote: true, approveAll: false,
38
+ interactive: false, execFileSync: options.execFileSync });
39
+ saveProgress(progress, { initialized: true, initializing: false });
40
+ }
41
+
42
+ function finishNewRemote(context, progress, options) {
43
+ const opts = { ...options, cwd: context.target };
44
+ if (!progress.state.initialCommit) {
45
+ ensureInitialCommit(context.target, opts);
46
+ const initialCommit = String(capture('git', ['rev-parse', 'HEAD'], opts)).trim();
47
+ saveProgress(progress, { initialCommit });
48
+ }
49
+ if (!progress.state.remoteCreated) {
50
+ github.createRepository(context.fullName, options);
51
+ saveProgress(progress, { remoteCreated: true });
52
+ }
53
+ if (!github.originIdentity(context.target, options)) {
54
+ checked('git', ['remote', 'add', 'origin', `https://github.com/${context.fullName}.git`],
55
+ 'Could not configure the GitHub origin. Local files are preserved.', opts);
56
+ }
57
+ github.pushInitialCommit(context, progress, saveProgress, options);
58
+ }
59
+
60
+ function cloneWorkspace(context, progress, options) {
61
+ if (progress.state.initialized) throw new Error('Previously initialized workspace is missing. Restore it before retrying.');
62
+ saveProgress(progress, { cloning: true });
63
+ github.cloneRepository(context, options);
64
+ saveProgress(progress, { initialized: true, cloning: false });
65
+ }
66
+
67
+ function prepareGithub(context, progress, options, existing) {
68
+ if (existing && github.originIdentity(context.target, options)) {
69
+ if (progress.state.remoteCreated && !progress.state.pushed) finishNewRemote(context, progress, options);
70
+ return false;
71
+ }
72
+ const remote = github.lookupRepository(context.fullName, options);
73
+ if (!remote) {
74
+ if (progress.state.remoteCreated) {
75
+ throw new Error('The existing remote is no longer accessible. Restore access before retrying.');
76
+ }
77
+ if (existing && !progress.state.initialCommit) {
78
+ throw new Error('Initialization stopped before recording the initial commit. Inspect the preserved workspace before creating its remote.');
79
+ }
80
+ if (!existing) initializeLocal(context, progress, options);
81
+ finishNewRemote(context, progress, options);
82
+ return true;
83
+ }
84
+ if (!existing) {
85
+ cloneWorkspace(context, progress, options);
86
+ } else if (!progress.state.remoteCreated) {
87
+ throw new Error('Repository appeared during creation. Inspect the local workspace and remote before connecting them.');
88
+ }
89
+ if (progress.state.remoteCreated && !progress.state.pushed) finishNewRemote(context, progress, options);
90
+ github.assertOrigin(context, options);
91
+ return false;
92
+ }
93
+
94
+ function prepareWorkspace(context, progress, options = {}) {
95
+ const existing = validateExisting(context, progress, options);
96
+ let installed = false;
97
+ if (context.fullName) installed = prepareGithub(context, progress, options, existing);
98
+ else if (!existing) {
99
+ if (progress.state.initialized) throw new Error('Previously initialized workspace is missing. Restore it before retrying.');
100
+ initializeLocal(context, progress, options);
101
+ installed = true;
102
+ }
103
+ if (!installed) {
104
+ const install = options.managed ? runUpdate : runInstall;
105
+ const preview = install({ cwd: context.target, host: context.code, approveAll: false, dryRun: true });
106
+ checkKit(preview);
107
+ checkKit(install({ cwd: context.target, host: context.code, approveAll: false }));
108
+ }
109
+ if (!fs.existsSync(context.target)) throw new Error('Workspace preparation did not create its directory.');
110
+ saveProgress(progress, { initialized: true, ready: true });
111
+ }
112
+
113
+ module.exports = { validateExisting, prepareWorkspace, checkKit };
@@ -0,0 +1,84 @@
1
+ /** Portable local-first agent onboarding. */
2
+ 'use strict';
3
+
4
+ const { randomUUID } = require('crypto');
5
+ const logger = require('../utils/logger');
6
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
7
+ const { auditLog } = require('../core/audit-logger');
8
+ const { normalizeSetup, preflight, resolveWork } = require('./setup-context');
9
+ const { acquireWorkspace } = require('./setup-state');
10
+ const { ensureController, ensureGithubLogin, ensureProvider } = require('./setup-auth');
11
+ const { prepareWorkspace, validateExisting } = require('./setup-workspace');
12
+ const { prepareSession, attachSession } = require('./setup-session');
13
+ const { managedSetupOptions } = require('./setup-managed');
14
+
15
+ async function authenticate(context, options, setPhase) {
16
+ setPhase('controller-login');
17
+ logger.log('Connecting to AI Fabrix...');
18
+ await ensureController(context);
19
+ if (context.fullName) {
20
+ setPhase('github-login');
21
+ ensureGithubLogin(options);
22
+ }
23
+ if (context.code === 'cursor') return null;
24
+ setPhase('agent-login');
25
+ return ensureProvider(context, options);
26
+ }
27
+
28
+ async function prepareOnboarding(context, progress, options) {
29
+ const correlationId = randomUUID();
30
+ let provider;
31
+ let session;
32
+ let phase = 'workspace-check';
33
+ try {
34
+ validateExisting(context, progress, options);
35
+ provider = await authenticate(context, options, value => {
36
+ phase = value;
37
+ });
38
+ phase = 'workspace';
39
+ prepareWorkspace(context, progress, options);
40
+ if (!options.managed || context.code === 'claude') {
41
+ phase = 'agent-start';
42
+ session = prepareSession(context, provider, options);
43
+ }
44
+ await auditLog('AUDIT', 'Agent workspace onboarding ready', { correlationId, path: context.target, agent: context.code });
45
+ } catch (error) {
46
+ await auditLog('ERROR', 'Agent workspace onboarding failed', { correlationId, phase });
47
+ throw error;
48
+ } finally {
49
+ progress.release();
50
+ }
51
+ return { session, provider };
52
+ }
53
+
54
+ async function runSetup(options = {}) {
55
+ const input = normalizeSetup(options);
56
+ preflight(input, options);
57
+ const context = await resolveWork(input, options);
58
+ const progress = acquireWorkspace(context);
59
+ const { session, provider } = await prepareOnboarding(context, progress, options);
60
+ logger.log(formatSuccessLine(`Workspace ready: ${context.target}`));
61
+ if (options.managed) {
62
+ const host = options.remoteHost || new URL(context.controller).hostname;
63
+ if (context.code === 'claude') {
64
+ logger.log(`Claude Code running in tmux session ${session}. Attach: tmux attach -t ${session}`);
65
+ logger.log(`Next: Open Claude Code Remote Control on ${host}, or attach to ${session}.`);
66
+ return { path: context.target, code: context.code, session };
67
+ }
68
+ const next = {
69
+ cursor: `In Cursor, connect with Remote SSH to ${host}, then open ${context.target}.`,
70
+ codex: `In Codex, add the SSH project ${context.target} on ${host}.`
71
+ }[context.code];
72
+ logger.log(`Next: ${next} Say "Hi" to begin.`);
73
+ } else {
74
+ attachSession(session, provider, options);
75
+ }
76
+ return { path: context.target, code: context.code, session };
77
+ }
78
+
79
+ async function runManagedOnboarding(options = {}) {
80
+ const managed = await managedSetupOptions(options);
81
+ return runSetup({ ...options, ...managed });
82
+ }
83
+
84
+ module.exports = { runSetup, runManagedOnboarding };
@@ -129,7 +129,7 @@ function buildTmuxSpec(p) {
129
129
  const remote = p.app === 'claude' && p.remoteControl;
130
130
  return {
131
131
  bin: 'tmux',
132
- args: [...mode, spec.bin, ...extra],
132
+ args: [...mode, ...(p.binary ? ['env', p.binary] : [spec.bin]), ...extra],
133
133
  session: p.session,
134
134
  appBin: spec.bin,
135
135
  needTmux: true,
@@ -287,16 +287,17 @@ function resolveStartContext(options) {
287
287
  const cwd = resolveGitRoot(options.cwd || process.cwd());
288
288
  const session = resolveSessionName(options.name, cwd);
289
289
  const remoteControlPrefix = hostUserPrefix(options);
290
- const remoteControlName = resolveRemoteControlName(remoteControlPrefix, session);
290
+ const remoteControlName = options.remoteControlName || resolveRemoteControlName(remoteControlPrefix, session);
291
291
  const remoteControl = options.remoteControl !== false;
292
292
  const spec = buildLaunchSpec({
293
293
  app, cwd, session, remoteControlPrefix, remoteControlName,
294
- detach: Boolean(options.detach), remoteControl
294
+ detach: Boolean(options.detach), remoteControl, binary: options.binary
295
295
  });
296
296
  spec.cwd = cwd;
297
297
  const remoteSpec = app === 'codex' && remoteControl
298
298
  ? buildCodexRemoteSpec(cwd)
299
299
  : undefined;
300
+ if (remoteSpec && options.binary) remoteSpec.bin = options.binary;
300
301
  return { app, cwd, session, remoteControl, spec, remoteSpec };
301
302
  }
302
303
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const { execFileSync } = require('child_process');
8
+ const { execFileSync, spawnSync } = require('child_process');
9
9
 
10
10
  const LIST_FORMAT = [
11
11
  '#{session_name}',
@@ -66,7 +66,7 @@ function isNoTmuxServer(error) {
66
66
  .filter(Boolean)
67
67
  .map(String)
68
68
  .join(' ');
69
- return /no server running|failed to connect to server/i.test(message);
69
+ return /no server running|failed to connect to server|error connecting to .*\/tmux-\d+\/.*No such file or directory/i.test(message);
70
70
  }
71
71
 
72
72
  /**
@@ -150,6 +150,22 @@ function runKill(options) {
150
150
  return { command: 'kill', session, killed: true };
151
151
  }
152
152
 
153
+ function runAttach(options) {
154
+ const exec = options.execFileSync || execFileSync;
155
+ const session = normalizeExactSessionName(options.name);
156
+ requireTmux(exec);
157
+ if (!tmuxSessionExists(session, exec)) {
158
+ throw new Error(`tmux session "${session}" is not running. Run: aifabrix agent-kit list`);
159
+ }
160
+ const insideTmux = Boolean((options.env || process.env).TMUX);
161
+ const args = [insideTmux ? 'switch-client' : 'attach-session', '-t', `=${session}`];
162
+ const result = (options.spawnSync || spawnSync)('tmux', args, { stdio: 'inherit' });
163
+ if (!result || result.error || result.signal || result.status !== 0) {
164
+ throw new Error(`Could not attach to tmux session "${session}".`);
165
+ }
166
+ return { command: 'attach', session, attached: true };
167
+ }
168
+
153
169
  module.exports = {
154
170
  LIST_FORMAT,
155
171
  hasOnPath,
@@ -159,5 +175,6 @@ module.exports = {
159
175
  tmuxSessionExists,
160
176
  parseSessionLine,
161
177
  runList,
162
- runKill
178
+ runKill,
179
+ runAttach
163
180
  };
@@ -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 | secret-reader | developer | docker)
201
+ * @param {Object} body - CreateUserDto: developerId, name, email, optional groups (admin | secret-manager | secret-reader | developer | docker | integrator)
202
202
  * @param {string} [serverCaPem] - Dev root CA PEM
203
203
  * @returns {Promise<Object>} UserResponseDto
204
204
  */
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Application-token health proof through the public Miso SDK transport.
3
+ * @fileoverview Never reads or returns the application token itself.
4
+ */
5
+ 'use strict';
6
+
7
+ /**
8
+ * POST /api/v1/auth/validate-client-token
9
+ * @requiresPermission {Controller} Client token
10
+ * @param {Object} client Initialized runtime client
11
+ * @param {AbortSignal} signal Probe cancellation
12
+ * @returns {Promise<void>} Resolves only for an authenticated application
13
+ */
14
+ async function validateMisoApplicationToken(client, signal) {
15
+ const response = await client.requestWithAuthStrategy(
16
+ 'POST', '/api/v1/auth/validate-client-token', { methods: ['client-token'] },
17
+ {}, { signal, timeout: 3000, maxRedirects: 0 }
18
+ );
19
+ if (response?.success === false || response?.data?.authenticated !== true) {
20
+ const error = new Error('Miso application authentication failed');
21
+ error.statusCode = 401;
22
+ throw error;
23
+ }
24
+ }
25
+
26
+ module.exports = { validateMisoApplicationToken };
@@ -30,7 +30,7 @@
30
30
  * @property {string} aifabrix-secrets - Local filesystem path or https URL for shared secrets (file vs remote API)
31
31
  * @property {string} aifabrix-env-config - Env config path
32
32
  * @property {string} remote-server - Builder-server base URL
33
- * @property {string} docker-endpoint - Docker API endpoint
33
+ * @property {string} [docker-endpoint] - Docker API endpoint; omit for SSH-only/integrator users
34
34
  * @property {string} [developer-root-domain] - App hostname root (e.g. aifabrix.dev → dev01.aifabrix.dev)
35
35
  * @property {boolean} [docker-tls-skip-verify] - When true, Docker uses DOCKER_TLS_VERIFY=0 even if ca.pem exists (client mTLS unchanged)
36
36
  * @property {string} sync-ssh-user - SSH user for Mutagen
@@ -46,7 +46,7 @@
46
46
  * @property {string} createdAt - ISO 8601
47
47
  * @property {boolean} certificateIssued - Whether cert was issued
48
48
  * @property {string} [certificateValidNotAfter] - Cert validity end (optional)
49
- * @property {string[]} groups - Access groups (admin, secret-manager, secret-reader, developer, docker)
49
+ * @property {string[]} groups - Access groups (admin, secret-manager, secret-reader, developer, docker, integrator)
50
50
  */
51
51
 
52
52
  /**
@@ -55,7 +55,7 @@
55
55
  * @property {string} developerId - Unique developer ID (numeric string)
56
56
  * @property {string} name - Display name
57
57
  * @property {string} email - Email
58
- * @property {string[]} [groups] - Default [developer]; tokens: admin, secret-manager, secret-reader, developer, docker
58
+ * @property {string[]} [groups] - Default [developer]; tokens: admin, secret-manager, secret-reader, developer, docker, integrator
59
59
  */
60
60
 
61
61
  /**
package/lib/app/deploy.js CHANGED
@@ -18,7 +18,6 @@ const {
18
18
 
19
19
  const fs = require('fs').promises;
20
20
  const chalk = require('chalk');
21
- const pushUtils = require('../deployment/push');
22
21
 
23
22
  const SEP = chalk.gray('────────────────────────────────────────');
24
23
  const logger = require('../utils/logger');
@@ -56,106 +55,6 @@ function validateAppName(appName) {
56
55
  }
57
56
  }
58
57
 
59
- /**
60
- * Validates push prerequisites
61
- * @async
62
- * @function validatePushPrerequisites
63
- * @param {string} appName - Application name
64
- * @param {string} registry - Registry URL
65
- * @throws {Error} If prerequisites are not met
66
- */
67
- async function validatePushPrerequisites(appName, registry) {
68
- if (!pushUtils.validateRegistryURL(registry)) {
69
- throw new Error(`Invalid registry URL format: ${registry}. Expected format: *.azurecr.io`);
70
- }
71
-
72
- if (!await pushUtils.checkLocalImageExists(appName, 'latest')) {
73
- throw new Error(`Docker image ${appName}:latest not found locally.\nRun 'aifabrix build ${appName}' first`);
74
- }
75
-
76
- if (!await pushUtils.checkAzureCLIInstalled()) {
77
- throw new Error('Azure CLI is not installed. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
78
- }
79
- }
80
-
81
- /**
82
- * Executes push operations
83
- * @async
84
- * @function executePush
85
- * @param {string} appName - Application name
86
- * @param {string} registry - Registry URL
87
- * @param {string[]} tags - Tags to push
88
- * @throws {Error} If push fails
89
- */
90
- async function executePush(appName, registry, tags) {
91
- if (await pushUtils.checkACRAuthentication(registry)) {
92
- logger.log(formatSuccessLine(`Already authenticated with ${registry}`));
93
- } else {
94
- await pushUtils.authenticateACR(registry);
95
- }
96
-
97
- await Promise.all(tags.map(async(tag) => {
98
- await pushUtils.tagImage(`${appName}:latest`, `${registry}/${appName}:${tag}`);
99
- await pushUtils.pushImage(`${registry}/${appName}:${tag}`, registry);
100
- }));
101
- }
102
-
103
- /**
104
- * Verifies push result
105
- * @function verifyPushResult
106
- * @param {string[]} tags - Tags that were pushed
107
- * @param {string} registry - Registry URL
108
- * @param {string} appName - Application name
109
- */
110
- function verifyPushResult(tags, registry, appName) {
111
- logger.log(formatSuccessParagraph(`Successfully pushed ${tags.length} tag(s) to ${registry}`));
112
- logger.log(chalk.gray(`Image: ${registry}/${appName}:*`));
113
- logger.log(chalk.gray(`Tags: ${tags.join(', ')}`));
114
- }
115
-
116
- /**
117
- * Pushes application image to Azure Container Registry
118
- * @async
119
- * @function pushApp
120
- * @param {string} appName - Name of the application
121
- * @param {Object} options - Push options (registry, tag)
122
- * @returns {Promise<void>} Resolves when push is complete
123
- */
124
- async function pushApp(appName, options = {}) {
125
- try {
126
- validateAppName(appName);
127
-
128
- const { getBuilderPath, resolveApplicationConfigPath } = require('../utils/paths');
129
- const { loadConfigFile } = require('../utils/config-format');
130
- const builderPath = getBuilderPath(appName);
131
- let config;
132
- try {
133
- const configPath = resolveApplicationConfigPath(builderPath);
134
- config = loadConfigFile(configPath);
135
- } catch (error) {
136
- throw new Error(`Failed to load configuration: ${error.message}\nRun 'aifabrix create ${appName}' first`);
137
- }
138
-
139
- const registry = options.registry || config.image?.registry;
140
- if (!registry) {
141
- throw new Error('Registry URL is required. Provide via --registry flag or configure in application.yaml under image.registry');
142
- }
143
-
144
- if (!/^[^.]+\.azurecr\.io$/.test(registry)) {
145
- throw new Error(`Invalid ACR URL format: ${registry}. Expected format: *.azurecr.io`);
146
- }
147
-
148
- const tags = options.tag ? options.tag.split(',').map(t => t.trim()) : ['latest'];
149
-
150
- await validatePushPrerequisites(appName, registry);
151
- await executePush(appName, registry, tags);
152
- verifyPushResult(tags, registry, appName);
153
-
154
- } catch (error) {
155
- throw new Error(`Failed to push application: ${error.message}`);
156
- }
157
- }
158
-
159
58
  /**
160
59
  * Generates and validates deployment manifest
161
60
  * @async
@@ -492,7 +391,6 @@ async function deployApp(appName, options = {}) {
492
391
  }
493
392
 
494
393
  module.exports = {
495
- pushApp,
496
394
  deployApp,
497
395
  validateAppName,
498
396
  validateImageIsPullable