@aifabrix/builder 2.46.0 → 2.47.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 (175) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +16 -4
  47. package/lib/cli/setup-app.js +54 -3
  48. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  49. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  50. package/lib/cli/setup-platform.js +1 -1
  51. package/lib/cli/setup-utility-help-after.js +4 -1
  52. package/lib/cli/setup-utility-resolve.js +8 -5
  53. package/lib/commands/app-logs.js +54 -26
  54. package/lib/commands/app.js +41 -0
  55. package/lib/commands/auth-status-dataplane-version.js +97 -61
  56. package/lib/commands/auth-status.js +2 -7
  57. package/lib/commands/channel.js +58 -0
  58. package/lib/commands/datasource.js +1 -1
  59. package/lib/commands/identity-apply-sync.js +49 -8
  60. package/lib/commands/identity-group-cli.js +66 -26
  61. package/lib/commands/identity-membership-role-cache.js +58 -37
  62. package/lib/commands/identity-shared.js +22 -1
  63. package/lib/commands/identity-user-cli.js +80 -34
  64. package/lib/commands/repair-auth-apply.js +30 -4
  65. package/lib/commands/repair-auth-config.js +8 -13
  66. package/lib/commands/repair-datasource-com-v2.js +15 -0
  67. package/lib/commands/repair-datasource-service-contract.js +206 -0
  68. package/lib/commands/repair-datasource.js +53 -18
  69. package/lib/commands/repair-env-template.js +12 -12
  70. package/lib/commands/repair-openapi-sync.js +26 -4
  71. package/lib/commands/repair-steps.js +28 -13
  72. package/lib/commands/repair-system-alignment.js +7 -4
  73. package/lib/commands/resource-type.js +371 -0
  74. package/lib/commands/setup-modes.js +1 -1
  75. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  76. package/lib/commands/test-trust-external.js +39 -90
  77. package/lib/commands/up-common.js +33 -1
  78. package/lib/commands/up-dataplane.js +8 -4
  79. package/lib/commands/upload.js +24 -3
  80. package/lib/commands/verify-trust-command-action.js +4 -1
  81. package/lib/commands/verify-trust-external.js +12 -20
  82. package/lib/core/config-setup-platform-mode.js +3 -3
  83. package/lib/core/setup-platform-mode.js +41 -6
  84. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  85. package/lib/datasource/capability/capability-manifest.js +254 -0
  86. package/lib/datasource/capability/capability-resolve.js +3 -4
  87. package/lib/datasource/capability/copy-operations.js +5 -3
  88. package/lib/datasource/capability/remove-operations.js +2 -1
  89. package/lib/datasource/capability/run-capability-copy.js +32 -4
  90. package/lib/datasource/capability/run-capability-diff.js +3 -3
  91. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  92. package/lib/datasource/capability/run-capability-edit.js +4 -7
  93. package/lib/datasource/capability/run-capability-relate.js +2 -4
  94. package/lib/datasource/capability/run-capability-remove.js +2 -4
  95. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  96. package/lib/datasource/datasource-identifier.js +2 -1
  97. package/lib/datasource/datasource-validate-display.js +1 -1
  98. package/lib/datasource/field-reference-validator.js +24 -3
  99. package/lib/datasource/orphan-profiles-validator.js +98 -0
  100. package/lib/datasource/validate.js +41 -23
  101. package/lib/external-system/download-helpers.js +87 -0
  102. package/lib/external-system/download.js +61 -122
  103. package/lib/external-system/generator.js +1 -59
  104. package/lib/external-system/test.js +10 -15
  105. package/lib/generator/builders.js +11 -1
  106. package/lib/generator/external-controller-manifest.js +5 -2
  107. package/lib/generator/split.js +3 -1
  108. package/lib/generator/wizard.js +1 -28
  109. package/lib/identity/fk-reference.js +65 -0
  110. package/lib/identity/identity-apply-core.js +13 -58
  111. package/lib/identity/identity-apply-phases.js +5 -1
  112. package/lib/identity/identity-display.js +243 -0
  113. package/lib/identity/identity-response-helpers.js +52 -0
  114. package/lib/identity/identity-sync-errors.js +62 -0
  115. package/lib/identity/resolve-user-id.js +129 -0
  116. package/lib/identity/username-default.js +31 -0
  117. package/lib/lifecycle/certification-progress.js +2 -2
  118. package/lib/lifecycle/report-display-trust.js +70 -0
  119. package/lib/lifecycle/report-display.js +14 -13
  120. package/lib/resolvers/datasource-resolver.js +2 -3
  121. package/lib/resolvers/resource-type-file.js +159 -0
  122. package/lib/schema/application-schema.json +16 -0
  123. package/lib/schema/deployment-rules.yaml +2 -1
  124. package/lib/schema/external-datasource.schema.json +372 -58
  125. package/lib/schema/external-system.schema.json +331 -2
  126. package/lib/schema/type/document-storage.json +9 -5
  127. package/lib/utils/app-run-containers.js +25 -2
  128. package/lib/utils/authentication-shape.js +224 -0
  129. package/lib/utils/build-copy.js +36 -5
  130. package/lib/utils/build-resolve-image.js +66 -12
  131. package/lib/utils/celery-companion-apps.js +53 -0
  132. package/lib/utils/cli-utils.js +4 -0
  133. package/lib/utils/compose-celery-worker.js +23 -0
  134. package/lib/utils/compose-generate-docker-compose.js +9 -1
  135. package/lib/utils/configuration-env-resolver.js +36 -0
  136. package/lib/utils/credential-secrets-env-kv.js +216 -0
  137. package/lib/utils/credential-secrets-env.js +52 -262
  138. package/lib/utils/dataplane-resolver.js +4 -115
  139. package/lib/utils/declarative-url-ports.js +24 -1
  140. package/lib/utils/docker-build.js +1 -4
  141. package/lib/utils/external-env-template.js +10 -3
  142. package/lib/utils/external-system-validators.js +8 -2
  143. package/lib/utils/help-builder.js +2 -0
  144. package/lib/utils/paths.js +25 -0
  145. package/lib/utils/platform-controller-url.js +2 -1
  146. package/lib/utils/platform-kv-ref.js +57 -0
  147. package/lib/utils/runtime-configuration-download.js +189 -0
  148. package/lib/utils/runtime-configuration-values.js +119 -0
  149. package/lib/utils/schema-loader.js +19 -4
  150. package/lib/utils/worker-sidecars.js +142 -0
  151. package/lib/validation/external-manifest-validator.js +2 -0
  152. package/lib/validation/runtime-configuration-rules.js +98 -0
  153. package/lib/validation/validate-display-dimensions.js +154 -0
  154. package/lib/validation/validate-display-sections.js +276 -0
  155. package/lib/validation/validate-display-steps.js +67 -0
  156. package/lib/validation/validate-display.js +24 -402
  157. package/package.json +1 -1
  158. package/templates/applications/dataplane/application.yaml +5 -0
  159. package/templates/applications/dataplane/env.template +26 -13
  160. package/templates/applications/dataplane/rbac.yaml +108 -1
  161. package/templates/applications/miso-controller/application.yaml +2 -2
  162. package/templates/applications/miso-controller/env.template +37 -122
  163. package/templates/applications/miso-controller/rbac.yaml +28 -13
  164. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  165. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  166. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  167. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  168. package/templates/channels/slack/install.template.md.hbs +49 -0
  169. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  170. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  171. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  172. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  173. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  174. package/templates/python/docker-compose.hbs +56 -0
  175. package/templates/typescript/docker-compose.hbs +52 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Orchestrate aifabrix app add-channel (plan 152).
3
+ *
4
+ * @fileoverview Channel install after communication external system deploy
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getChannelTarget, getDefaultChannelDescription } = require('./channel-targets');
12
+ const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
13
+ const { generateChannelArtifact } = require('./channel-artifact-generator');
14
+ const {
15
+ getExistingChannelExtension,
16
+ buildChannelExtensionBlock,
17
+ fetchCommunicationSystem,
18
+ patchChannelExtension
19
+ } = require('./channel-external-system');
20
+ const {
21
+ auditArtifactGenerated,
22
+ auditConfigPatched,
23
+ auditApprovalRequired,
24
+ auditValidated,
25
+ auditFailed
26
+ } = require('./channel-audit');
27
+ const { runChannelProbe } = require('./channel-probe');
28
+ const { resolveChannelAuthContext } = require('./channel-auth-context');
29
+ const { ensureChannelCredential } = require('./channel-credential');
30
+ const {
31
+ normalizeBaseChannelName,
32
+ resolveChannelDisplayName
33
+ } = require('./channel-display-name');
34
+ const {
35
+ displayExistingChannelStatus,
36
+ displayChannelInstallResult,
37
+ displayChannelError
38
+ } = require('./channel-status-display');
39
+
40
+ /**
41
+ * @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
42
+ * @param {Object} channelExt
43
+ * @param {Object} [context]
44
+ * @returns {string|null}
45
+ */
46
+ function resolveApprovalUrl(target, channelExt, context = {}) {
47
+ if (!target.approvalRequired) return null;
48
+ if (channelExt?.status === 'installed' || channelExt?.status === 'validated') return null;
49
+ if (target.key === 'slack' && context.artifactPath) {
50
+ return `https://api.slack.com/apps/new — upload manifest: ${context.artifactPath}`;
51
+ }
52
+ if (target.key === 'teams-bot') {
53
+ return 'https://admin.teams.microsoft.com/ — configure Bot Channel Registration for this tenant';
54
+ }
55
+ return `https://platform-admin.example/configure/${target.key}`;
56
+ }
57
+
58
+ /**
59
+ * @param {Object} target
60
+ * @param {Object} channelExt
61
+ * @param {boolean} dryRun
62
+ * @returns {string}
63
+ */
64
+ function resolveInstallStatus(target, channelExt, dryRun) {
65
+ if (dryRun) return 'generated';
66
+ if (channelExt?.status === 'approvalRequired') return 'approvalRequired';
67
+ if (target.approvalRequired && !channelExt) return 'approvalRequired';
68
+ if (target.approvalRequired && channelExt?.status !== 'installed') return 'approvalRequired';
69
+ return 'installed';
70
+ }
71
+
72
+ /**
73
+ * @param {Object|null} validation
74
+ * @returns {Object|null}
75
+ */
76
+ function toValidationDisplay(validation) {
77
+ if (!validation) return null;
78
+ return {
79
+ callbackReachable: validation.callbackReachable,
80
+ credentialValid: validation.credentialValid,
81
+ assistantCatalogReachable: validation.assistantCatalogReachable,
82
+ inboundVerification: Object.values(validation.inboundVerification).every(Boolean)
83
+ };
84
+ }
85
+
86
+ /**
87
+ * @param {Object} params
88
+ * @returns {Object|null}
89
+ */
90
+ function showExistingChannelStatus(params) {
91
+ const { existing, options, target, channelTarget, name, baseName } = params;
92
+ if (!existing || options.force || options.dryRun) return null;
93
+ const sameInstall =
94
+ (existing.baseName && existing.baseName === baseName) ||
95
+ (!existing.baseName && existing.name === name);
96
+ if (!sameInstall) {
97
+ throw new Error(
98
+ `Channel already configured as "${existing.name}". ` +
99
+ `Use --force to replace with "${name}".`
100
+ );
101
+ }
102
+ displayExistingChannelStatus({
103
+ channelTarget,
104
+ name: existing.name || name,
105
+ channelExt: existing,
106
+ approvalUrl: resolveApprovalUrl(target, existing)
107
+ });
108
+ return { skipped: true, channelExt: existing };
109
+ }
110
+
111
+ /**
112
+ * @param {string} channelTarget
113
+ * @param {Object} artifactParams
114
+ * @returns {Object}
115
+ */
116
+ function generateArtifactWithAudit(channelTarget, artifactParams) {
117
+ try {
118
+ const artifact = generateChannelArtifact(artifactParams);
119
+ auditArtifactGenerated(channelTarget, {
120
+ artifactHash: artifact.artifactHash,
121
+ outputPath: artifact.outputPath,
122
+ dryRun: artifactParams.dryRun === true
123
+ });
124
+ return artifact;
125
+ } catch (err) {
126
+ auditFailed(channelTarget, { phase: 'artifact', error: err.message });
127
+ throw err;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * @param {Object} ctx
133
+ * @param {string} channelTarget
134
+ * @param {Object} channelBlock
135
+ * @param {string} status
136
+ * @param {string} artifactHash
137
+ * @returns {Promise<void>}
138
+ */
139
+ async function patchConfigWithAudit(ctx, channelTarget, channelBlock, status, artifactHash) {
140
+ try {
141
+ await patchChannelExtension(
142
+ { dataplaneUrl: ctx.dataplaneUrl, authConfig: ctx.authConfig, channelTarget },
143
+ channelBlock
144
+ );
145
+ auditConfigPatched(channelTarget, { status, artifactHash });
146
+ } catch (err) {
147
+ auditFailed(channelTarget, { phase: 'patch', error: err.message });
148
+ throw err;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * @param {Object} ctx
154
+ * @param {Object} channelBlock
155
+ * @param {string} channelTarget
156
+ * @param {boolean} probe
157
+ * @returns {Promise<Object|null>}
158
+ */
159
+ async function maybeRunProbe(ctx, channelBlock, channelTarget, probe) {
160
+ if (!probe) return null;
161
+ const validation = await runChannelProbe(
162
+ { dataplaneUrl: ctx.dataplaneUrl, authConfig: ctx.authConfig },
163
+ channelBlock
164
+ );
165
+ if (validation.passed) {
166
+ auditValidated(channelTarget, { probe: true });
167
+ } else {
168
+ auditFailed(channelTarget, { phase: 'probe', validation });
169
+ }
170
+ return validation;
171
+ }
172
+
173
+ /**
174
+ * @param {string} channelTarget
175
+ * @param {Object} options
176
+ * @returns {Promise<{ target: Object, name: string, baseName: string, description: string, ctx: Object, existing: Object|null }>}
177
+ */
178
+ async function prepareChannelContext(channelTarget, options) {
179
+ const target = getChannelTarget(channelTarget);
180
+ if (target.experimental) {
181
+ throw new Error(
182
+ `Channel target "${channelTarget}" is experimental. Confirm product requirement before use.`
183
+ );
184
+ }
185
+ if (!options.name || typeof options.name !== 'string' || !options.name.trim()) {
186
+ throw new Error('--name is required for app add-channel');
187
+ }
188
+
189
+ const baseName = normalizeBaseChannelName(options.name);
190
+ const ctx = await resolveChannelAuthContext(channelTarget, { silent: options.quiet });
191
+ const name = resolveChannelDisplayName(baseName, ctx.environment);
192
+ const description =
193
+ options.description && String(options.description).trim()
194
+ ? String(options.description).trim()
195
+ : getDefaultChannelDescription();
196
+
197
+ const system = await fetchCommunicationSystem({
198
+ dataplaneUrl: ctx.dataplaneUrl,
199
+ authConfig: ctx.authConfig,
200
+ channelTarget
201
+ });
202
+ const existing = getExistingChannelExtension(system);
203
+ return { target, name, baseName, description, ctx, existing };
204
+ }
205
+
206
+ /**
207
+ * @param {Object} params
208
+ * @returns {Promise<Object>}
209
+ */
210
+ async function completeLiveInstall(params) {
211
+ const { ctx, channelTarget, channelBlock, status, artifact, target, name, baseName, options } = params;
212
+
213
+ await ensureChannelCredential({
214
+ ctx,
215
+ channelTarget,
216
+ target,
217
+ force: options.force === true
218
+ });
219
+
220
+ await patchConfigWithAudit(ctx, channelTarget, channelBlock, status, artifact.artifactHash);
221
+
222
+ const approvalUrl =
223
+ status === 'approvalRequired'
224
+ ? resolveApprovalUrl(target, channelBlock, { artifactPath: artifact.outputPath })
225
+ : null;
226
+ if (approvalUrl) {
227
+ auditApprovalRequired(channelTarget, { approvalUrl });
228
+ }
229
+
230
+ const validation = await maybeRunProbe(ctx, channelBlock, channelTarget, options.probe);
231
+
232
+ displayChannelInstallResult({
233
+ channelTarget,
234
+ name,
235
+ baseName,
236
+ statusLabel: status === 'approvalRequired' ? 'Approval required' : 'Installed',
237
+ credentialKey: target.credentialKey,
238
+ capabilities: target.capabilities,
239
+ artifactPath: artifact.outputPath,
240
+ installGuidePath: artifact.installPath,
241
+ validation: toValidationDisplay(validation),
242
+ approvalUrl
243
+ });
244
+
245
+ return { channelBlock, artifact, validation, approvalUrl };
246
+ }
247
+
248
+ /**
249
+ * @param {Object} params
250
+ * @returns {{ dryRun: true, channelBlock: Object, artifact: Object }}
251
+ */
252
+ function finishDryRunInstall(params) {
253
+ displayChannelInstallResult({
254
+ channelTarget: params.channelTarget,
255
+ name: params.name,
256
+ baseName: params.baseName,
257
+ statusLabel: 'Generated (dry-run)',
258
+ credentialKey: params.target.credentialKey,
259
+ capabilities: params.target.capabilities,
260
+ artifactPath: params.artifact.outputPath,
261
+ installGuidePath: params.artifact.installPath
262
+ });
263
+ return { dryRun: true, channelBlock: params.channelBlock, artifact: params.artifact };
264
+ }
265
+
266
+ /**
267
+ * @param {string} channelTarget
268
+ * @param {Object} options
269
+ * @returns {Promise<Object>}
270
+ */
271
+ async function runAddChannel(channelTarget, options = {}) {
272
+ const { target, name, baseName, description, ctx, existing } = await prepareChannelContext(
273
+ channelTarget,
274
+ options
275
+ );
276
+
277
+ const skipped = showExistingChannelStatus({
278
+ existing,
279
+ options,
280
+ target,
281
+ channelTarget,
282
+ name,
283
+ baseName
284
+ });
285
+ if (skipped) return skipped;
286
+
287
+ const runtimeValues = resolveChannelRuntimeValues({
288
+ dataplaneBaseUrl: ctx.dataplaneUrl,
289
+ channelTarget,
290
+ appName: name,
291
+ baseName,
292
+ environment: ctx.environment,
293
+ appDescription: description,
294
+ icon: options.icon
295
+ });
296
+
297
+ const projectRoot = options.projectRoot || process.cwd();
298
+ const artifact = generateArtifactWithAudit(channelTarget, {
299
+ projectRoot,
300
+ channelTarget,
301
+ runtimeValues,
302
+ dryRun: options.dryRun === true
303
+ });
304
+
305
+ const status = resolveInstallStatus(target, existing, options.dryRun === true);
306
+ const channelBlock = buildChannelExtensionBlock(
307
+ { name, baseName, description, artifactHash: artifact.artifactHash, status },
308
+ target
309
+ );
310
+
311
+ if (options.dryRun) {
312
+ return finishDryRunInstall({ channelTarget, name, baseName, target, artifact, channelBlock });
313
+ }
314
+
315
+ return completeLiveInstall({
316
+ ctx,
317
+ channelTarget,
318
+ channelBlock,
319
+ status,
320
+ artifact,
321
+ target,
322
+ name,
323
+ baseName,
324
+ options
325
+ });
326
+ }
327
+
328
+ /**
329
+ * @param {string} channelTarget
330
+ * @param {Object} options
331
+ */
332
+ async function addChannelCommand(channelTarget, options = {}) {
333
+ try {
334
+ await runAddChannel(channelTarget, options);
335
+ } catch (err) {
336
+ displayChannelError(err.message || String(err));
337
+ throw err;
338
+ }
339
+ }
340
+
341
+ module.exports = {
342
+ runAddChannel,
343
+ addChannelCommand,
344
+ resolveApprovalUrl,
345
+ resolveInstallStatus,
346
+ normalizeBaseChannelName,
347
+ resolveChannelDisplayName
348
+ };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Generate channel artifacts from Handlebars templates (plan 152).
3
+ *
4
+ * @fileoverview Channel artifact generation — dist/channels, sha256 hash, no secrets
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const crypto = require('crypto');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const Handlebars = require('handlebars');
15
+ const { getChannelTarget } = require('./channel-targets');
16
+
17
+ const SECRET_PATTERNS = [/xoxb-/i, /xoxp-/i, /client_secret/i, /signing_secret/i, /Bearer\s+/i];
18
+
19
+ /**
20
+ * @param {string} content
21
+ * @throws {Error}
22
+ */
23
+ function assertNoSecretsInArtifact(content) {
24
+ for (const pattern of SECRET_PATTERNS) {
25
+ if (pattern.test(content)) {
26
+ throw new Error('Generated artifact must not contain secret-like values');
27
+ }
28
+ }
29
+ if (content.includes('your-domain.com')) {
30
+ throw new Error('Generated artifact still contains placeholder domain your-domain.com');
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @param {string} projectRoot
36
+ * @param {string} channelTarget
37
+ * @returns {string}
38
+ */
39
+ function getTemplatesDir(projectRoot, channelTarget) {
40
+ const target = getChannelTarget(channelTarget);
41
+ return path.join(projectRoot, 'templates', 'channels', target.templateDir);
42
+ }
43
+
44
+ /**
45
+ * @param {string} projectRoot
46
+ * @param {string} channelTarget
47
+ * @returns {string}
48
+ */
49
+ function getArtifactOutputPath(projectRoot, channelTarget) {
50
+ const target = getChannelTarget(channelTarget);
51
+ return path.join(
52
+ projectRoot,
53
+ 'dist',
54
+ 'channels',
55
+ channelTarget,
56
+ target.artifactFileName
57
+ );
58
+ }
59
+
60
+ /**
61
+ * @param {string} templatePath
62
+ * @param {Object} context
63
+ * @returns {string}
64
+ */
65
+ function renderTemplateFile(templatePath, context) {
66
+ if (!fs.existsSync(templatePath)) {
67
+ throw new Error(`Channel template not found: ${templatePath}`);
68
+ }
69
+ const raw = fs.readFileSync(templatePath, 'utf8');
70
+ const template = Handlebars.compile(raw);
71
+ return template(context);
72
+ }
73
+
74
+ /**
75
+ * @param {string} templatesDir
76
+ * @param {Object} runtimeValues
77
+ * @param {string} outputDir
78
+ * @param {boolean} dryRun
79
+ * @returns {{ installPath: string, content: string }|null}
80
+ */
81
+ function renderInstallGuideIfPresent(templatesDir, runtimeValues, outputDir, dryRun) {
82
+ const installTemplate = path.join(templatesDir, 'install.template.md.hbs');
83
+ if (!fs.existsSync(installTemplate)) {
84
+ return null;
85
+ }
86
+ const content = renderTemplateFile(installTemplate, runtimeValues);
87
+ assertNoSecretsInArtifact(content);
88
+ const installPath = path.join(outputDir, 'INSTALL.md');
89
+ if (!dryRun) {
90
+ fs.mkdirSync(outputDir, { recursive: true });
91
+ fs.writeFileSync(installPath, content, 'utf8');
92
+ }
93
+ return { installPath, content };
94
+ }
95
+
96
+ /**
97
+ * @param {Object} params
98
+ * @param {string} params.projectRoot
99
+ * @param {string} params.channelTarget
100
+ * @param {Object} params.runtimeValues
101
+ * @param {boolean} [params.dryRun]
102
+ * @returns {{ outputPath: string, artifactHash: string, content: string, installPath?: string }}
103
+ */
104
+ function generateChannelArtifact(params) {
105
+ const templatesDir = getTemplatesDir(params.projectRoot, params.channelTarget);
106
+ const manifestTemplate = path.join(templatesDir, 'manifest.template.json.hbs');
107
+ const content = renderTemplateFile(manifestTemplate, params.runtimeValues);
108
+ assertNoSecretsInArtifact(content);
109
+
110
+ const outputPath = getArtifactOutputPath(params.projectRoot, params.channelTarget);
111
+ const artifactHash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
112
+ const outputDir = path.dirname(outputPath);
113
+
114
+ if (!params.dryRun) {
115
+ fs.mkdirSync(outputDir, { recursive: true });
116
+ fs.writeFileSync(outputPath, content, 'utf8');
117
+ }
118
+
119
+ const install = renderInstallGuideIfPresent(
120
+ templatesDir,
121
+ params.runtimeValues,
122
+ outputDir,
123
+ params.dryRun === true
124
+ );
125
+
126
+ return {
127
+ outputPath,
128
+ artifactHash,
129
+ content,
130
+ installPath: install?.installPath
131
+ };
132
+ }
133
+
134
+ module.exports = {
135
+ assertNoSecretsInArtifact,
136
+ getTemplatesDir,
137
+ getArtifactOutputPath,
138
+ renderTemplateFile,
139
+ renderInstallGuideIfPresent,
140
+ generateChannelArtifact
141
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Structured audit logging for channel install (plan 152).
3
+ *
4
+ * @fileoverview Channel install audit events — no secrets in payloads
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const logger = require('../utils/logger');
12
+
13
+ /**
14
+ * @param {string} eventName
15
+ * @param {Object} payload
16
+ */
17
+ function emitChannelAuditEvent(eventName, payload) {
18
+ logger.log(
19
+ JSON.stringify({
20
+ event: eventName,
21
+ ...payload,
22
+ timestamp: new Date().toISOString()
23
+ })
24
+ );
25
+ }
26
+
27
+ /**
28
+ * @param {string} channelTarget
29
+ * @param {Object} details
30
+ */
31
+ function auditArtifactGenerated(channelTarget, details) {
32
+ emitChannelAuditEvent('channelArtifactGenerated', { channelTarget, ...details });
33
+ }
34
+
35
+ /**
36
+ * @param {string} channelTarget
37
+ * @param {Object} details
38
+ */
39
+ function auditConfigPatched(channelTarget, details) {
40
+ emitChannelAuditEvent('channelConfigPatched', { channelTarget, ...details });
41
+ }
42
+
43
+ /**
44
+ * @param {string} channelTarget
45
+ * @param {Object} details
46
+ */
47
+ function auditCredentialCreated(channelTarget, details) {
48
+ emitChannelAuditEvent('channelCredentialCreated', { channelTarget, ...details });
49
+ }
50
+
51
+ /**
52
+ * @param {string} channelTarget
53
+ * @param {Object} details
54
+ */
55
+ function auditCredentialUpdated(channelTarget, details) {
56
+ emitChannelAuditEvent('channelCredentialUpdated', { channelTarget, ...details });
57
+ }
58
+
59
+ /**
60
+ * @param {string} channelTarget
61
+ * @param {Object} details
62
+ */
63
+ function auditApprovalRequired(channelTarget, details) {
64
+ emitChannelAuditEvent('channelApprovalRequired', { channelTarget, ...details });
65
+ }
66
+
67
+ /**
68
+ * @param {string} channelTarget
69
+ * @param {Object} details
70
+ */
71
+ function auditApprovalCompleted(channelTarget, details) {
72
+ emitChannelAuditEvent('channelApprovalCompleted', { channelTarget, ...details });
73
+ }
74
+
75
+ /**
76
+ * @param {string} channelTarget
77
+ * @param {Object} details
78
+ */
79
+ function auditValidated(channelTarget, details) {
80
+ emitChannelAuditEvent('channelValidated', { channelTarget, ...details });
81
+ }
82
+
83
+ /**
84
+ * @param {string} channelTarget
85
+ * @param {Object} details
86
+ */
87
+ function auditFailed(channelTarget, details) {
88
+ emitChannelAuditEvent('channelFailed', { channelTarget, ...details });
89
+ }
90
+
91
+ module.exports = {
92
+ emitChannelAuditEvent,
93
+ auditArtifactGenerated,
94
+ auditConfigPatched,
95
+ auditCredentialCreated,
96
+ auditCredentialUpdated,
97
+ auditApprovalRequired,
98
+ auditApprovalCompleted,
99
+ auditValidated,
100
+ auditFailed
101
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Resolve controller, dataplane, and auth for channel install.
3
+ *
4
+ * @fileoverview Channel install auth context (plan 152)
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const config = require('../core/config');
12
+ const { resolveControllerUrl } = require('../utils/controller-url');
13
+ const { getDeploymentAuth } = require('../utils/token-manager');
14
+ const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
15
+
16
+ /**
17
+ * @async
18
+ * @param {string} channelTarget
19
+ * @param {{ silent?: boolean }} [opts]
20
+ * @returns {Promise<{ controllerUrl: string, environment: string, dataplaneUrl: string, authConfig: Object }>}
21
+ */
22
+ async function resolveChannelAuthContext(channelTarget, opts = {}) {
23
+ const controllerUrl = await resolveControllerUrl();
24
+ const environment = (await config.getEnvironment()) || 'dev';
25
+ const authConfig = await getDeploymentAuth(controllerUrl, environment, channelTarget);
26
+ if (!authConfig) {
27
+ throw new Error('Authentication required. Run "aifabrix login" first.');
28
+ }
29
+ const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, {
30
+ silent: opts.silent === true
31
+ });
32
+ return { controllerUrl, environment, dataplaneUrl, authConfig };
33
+ }
34
+
35
+ module.exports = {
36
+ resolveChannelAuthContext
37
+ };