@aifabrix/builder 2.44.6 → 2.45.5

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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -10,45 +10,12 @@ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
10
10
  const path = require('path');
11
11
  const fs = require('fs');
12
12
  const chalk = require('chalk');
13
- const secrets = require('../core/secrets');
14
13
  const generator = require('../generator');
15
14
  const logger = require('../utils/logger');
16
15
  const { handleCommandError, logOfflinePathWhenType } = require('../utils/cli-utils');
17
16
  const { detectAppType, getDeployJsonPath, getResolveAppPath } = require('../utils/paths');
18
-
19
- const JSON_HELP_AFTER = `
20
- Example:
21
- $ aifabrix json myapp
22
- Generates *-deploy.json (or application-schema.json) for commit before deploy.
23
- `;
24
-
25
- const VALIDATE_HELP_AFTER = `
26
- Examples:
27
- $ aifabrix validate myapp
28
- $ aifabrix validate myapp --cert-sync
29
- $ aifabrix validate --integration
30
- $ aifabrix validate --builder
31
- `;
32
-
33
- const REPAIR_HELP_AFTER = `
34
- Examples:
35
- $ aifabrix repair hubspot-demo
36
- Align manifest, system/datasource files, RBAC extract, env.template, and deploy JSON under integration/<systemKey>/.
37
-
38
- $ aifabrix repair hubspot-demo --dry-run
39
- Show what would change without writing files.
40
-
41
- $ aifabrix repair hubspot-demo --auth oauth2
42
- Set authentication method; updates the system file and env.template (oauth2, aad, apikey, basic, …).
43
-
44
- $ aifabrix repair hubspot-demo --rbac --expose --sync --test
45
- Optional datasource fixes: RBAC roles/permissions, exposed.schema from attributes, default sync block, testPayload stubs.
46
-
47
- $ aifabrix repair hubspot-demo --doc
48
- Regenerate README.md from the current deployment manifest only (other drift fixes unchanged).
49
-
50
- Before overwriting files, repair writes timestamped backups under integration/<systemKey>/backup/ (same as datasource capability copy). Use --no-backup to skip.
51
- `;
17
+ const { setupResolveCommand } = require('./setup-utility-resolve');
18
+ const { JSON_HELP_AFTER, VALIDATE_HELP_AFTER, REPAIR_HELP_AFTER } = require('./setup-utility-help-after');
52
19
 
53
20
  /**
54
21
  * Resolve app path and type for split-json (integration first, then builder).
@@ -121,46 +88,20 @@ function logSplitJsonResult(result) {
121
88
  }
122
89
  }
123
90
 
124
- function setupResolveCommand(program) {
125
- program.command('resolve <app>')
126
- .description('Generate .env from template; optional validate after')
127
- .option('-f, --force', 'Generate missing secret keys in secrets file')
128
- .option('--skip-validation', 'Skip file validation after generating .env')
129
- .action(async(appName, options) => {
130
- try {
131
- const { appPath, envOnly } = await getResolveAppPath(appName);
132
- const envPath = await secrets.generateEnvFile(
133
- appName,
134
- undefined,
135
- 'docker',
136
- options.force,
137
- { appPath, envOnly, skipOutputPath: false, preserveFromPath: null }
138
- );
139
- logger.log(`✔ Generated .env file: ${envPath}`);
140
- if (envOnly) {
141
- logger.log(chalk.gray(' (env-only mode: validation skipped; no application config file)'));
142
- } else if (!options.skipValidation) {
143
- const validate = require('../validation/validate');
144
- const result = await validate.validateAppOrFile(appName);
145
- validate.displayValidationResults(result);
146
- if (!result.valid) {
147
- logger.log(chalk.yellow('\n⚠ Validation found errors. Fix them before deploying.'));
148
- process.exit(1);
149
- }
150
- }
151
- } catch (error) {
152
- handleCommandError(error, 'resolve');
153
- process.exit(1);
154
- }
155
- });
156
- }
157
-
158
91
  function setupJsonCommand(program) {
159
- program.command('json <app>')
92
+ program.command('json <appKey|systemKey>')
160
93
  .description('Write deployment JSON to disk for version control')
161
94
  .addHelpText('after', JSON_HELP_AFTER)
162
95
  .action(async(appName, options) => {
163
96
  try {
97
+ const resolved = await getResolveAppPath(appName);
98
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
99
+ emitManifestMetadataLineIfTTY(logger, {
100
+ appKey: appName,
101
+ appPath: resolved.appPath,
102
+ envOnly: resolved.envOnly,
103
+ json: false
104
+ });
164
105
  const result = await generator.generateDeployJsonWithValidation(appName, options);
165
106
  if (result.success) {
166
107
  const fileName = result.path.includes('application-schema.json') ? 'application-schema.json' : 'deployment JSON';
@@ -182,7 +123,7 @@ function setupJsonCommand(program) {
182
123
  }
183
124
 
184
125
  function setupSplitJsonCommand(program) {
185
- program.command('split-json <app>')
126
+ program.command('split-json <appKey|systemKey>')
186
127
  .description('Split deploy JSON into env.template, application.yaml, rbac, README, …')
187
128
  .option('-o, --output <dir>', 'Output directory for component files (defaults to same directory as JSON)')
188
129
  .action(async(appName, options) => {
@@ -289,32 +230,54 @@ function setupRepairCommand(program) {
289
230
  });
290
231
  }
291
232
 
233
+ async function resolveConvertTargetFormat(options) {
234
+ const config = require('../core/config');
235
+ const effectiveFormat = options.format || (await config.getFormat());
236
+ if (!effectiveFormat) {
237
+ throw new Error(
238
+ 'Option --format is required and must be \'json\' or \'yaml\' (or set default with aifabrix dev set-format)'
239
+ );
240
+ }
241
+ const normalized = effectiveFormat.trim().toLowerCase();
242
+ if (normalized !== 'json' && normalized !== 'yaml') {
243
+ throw new Error('Option --format must be \'json\' or \'yaml\'');
244
+ }
245
+ return normalized;
246
+ }
247
+
248
+ function logConvertCommandResults(converted, deleted) {
249
+ logger.log(formatSuccessParagraph('Convert complete.'));
250
+ converted.forEach((p) => logger.log(` • ${p}`));
251
+ if (deleted.length > 0) {
252
+ logger.log(chalk.gray(' Removed old files:'));
253
+ deleted.forEach((p) => logger.log(chalk.gray(` ${p}`)));
254
+ }
255
+ }
256
+
257
+ async function runConvertCommandForApp(appName, options) {
258
+ const normalized = await resolveConvertTargetFormat(options);
259
+ const { isProtectionScope } = require('../protection/scope');
260
+ if (isProtectionScope(appName)) {
261
+ const { runConvertProtectionBatch } = require('../protection/convert-batch');
262
+ const { converted, deleted } = await runConvertProtectionBatch(normalized, {
263
+ force: options.force
264
+ });
265
+ logConvertCommandResults(converted, deleted);
266
+ return;
267
+ }
268
+ const { runConvert } = require('../commands/convert');
269
+ const { converted, deleted } = await runConvert(appName, { format: normalized, force: options.force });
270
+ logConvertCommandResults(converted, deleted);
271
+ }
272
+
292
273
  function setupConvertCommand(program) {
293
- program.command('convert <app>')
294
- .description('Convert integration config files between JSON and YAML')
274
+ program.command('convert <appKey|systemKey>')
275
+ .description('Convert config files between JSON and YAML (builder/<appKey>/ or integration/<systemKey>/)')
295
276
  .option('--format <format>', 'Target format: json | yaml (required unless config format is set)')
296
277
  .option('-f, --force', 'Skip confirmation prompt')
297
278
  .action(async(appName, options) => {
298
279
  try {
299
- const config = require('../core/config');
300
- const effectiveFormat = options.format || (await config.getFormat());
301
- if (!effectiveFormat) {
302
- throw new Error(
303
- 'Option --format is required and must be \'json\' or \'yaml\' (or set default with aifabrix dev set-format)'
304
- );
305
- }
306
- const normalized = effectiveFormat.trim().toLowerCase();
307
- if (normalized !== 'json' && normalized !== 'yaml') {
308
- throw new Error('Option --format must be \'json\' or \'yaml\'');
309
- }
310
- const { runConvert } = require('../commands/convert');
311
- const { converted, deleted } = await runConvert(appName, { format: normalized, force: options.force });
312
- logger.log(formatSuccessParagraph('Convert complete.'));
313
- converted.forEach(p => logger.log(` • ${p}`));
314
- if (deleted.length > 0) {
315
- logger.log(chalk.gray(' Removed old files:'));
316
- deleted.forEach(p => logger.log(chalk.gray(` ${p}`)));
317
- }
280
+ await runConvertCommandForApp(appName, options);
318
281
  } catch (error) {
319
282
  handleCommandError(error, 'convert');
320
283
  process.exit(1);
@@ -323,8 +286,8 @@ function setupConvertCommand(program) {
323
286
  }
324
287
 
325
288
  function setupShowCommand(program) {
326
- program.command('show <app>')
327
- .description('Show app from local tree (default) or controller (--online)')
289
+ program.command('show <appKey|systemKey>')
290
+ .description('Show builder app or external system from local tree (default) or controller (--online)')
328
291
  .option('--online', 'Fetch application data from the controller')
329
292
  .option('--json', 'Output as JSON')
330
293
  .option(
@@ -353,52 +316,121 @@ function setupSplitJsonConvertShowCommands(program) {
353
316
  setupShowCommand(program);
354
317
  }
355
318
 
356
- async function runValidateCommand(appOrFile, options) {
357
- const validate = require('../validation/validate');
358
- const opts = options.opts ? options.opts() : options;
319
+ function emitManifestAfterValidateIfApplicable(appOrFile, result, outFormat) {
320
+ if (outFormat === 'json' || !result?.appPath || !appOrFile || typeof appOrFile !== 'string') {
321
+ return;
322
+ }
323
+ try {
324
+ if (fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile()) {
325
+ return;
326
+ }
327
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
328
+ emitManifestMetadataLineIfTTY(logger, {
329
+ appKey: appOrFile,
330
+ appPath: result.appPath,
331
+ envOnly: false,
332
+ json: false
333
+ });
334
+ } catch {
335
+ /* ignore emit failures */
336
+ }
337
+ }
338
+
339
+ async function runValidateBatchBranch(validate, opts) {
359
340
  const integration = opts.integration === true;
360
341
  const builder = opts.builder === true;
342
+ if (!integration && !builder) {
343
+ return false;
344
+ }
361
345
  const outFormat = (opts.format || 'default').toLowerCase();
346
+ const batchResult = integration && builder
347
+ ? await validate.validateAll(opts)
348
+ : integration
349
+ ? await validate.validateAllIntegrations(opts)
350
+ : await validate.validateAllBuilderApps(opts);
351
+ if (outFormat === 'json') {
352
+ logger.log(JSON.stringify(batchResult, null, 2));
353
+ } else {
354
+ validate.displayBatchValidationResults(batchResult);
355
+ }
356
+ if (!batchResult.valid) process.exit(1);
357
+ return true;
358
+ }
362
359
 
363
- if (integration || builder) {
364
- const batchResult = integration && builder
365
- ? await validate.validateAll(opts)
366
- : integration
367
- ? await validate.validateAllIntegrations(opts)
368
- : await validate.validateAllBuilderApps(opts);
369
- if (outFormat === 'json') {
370
- logger.log(JSON.stringify(batchResult, null, 2));
371
- } else {
372
- validate.displayBatchValidationResults(batchResult);
360
+ async function runProtectionValidateBranch(appOrFile, opts) {
361
+ const { validateProtectionBatchCommand } = require('../protection/validate-batch');
362
+ const code = await validateProtectionBatchCommand(
363
+ {
364
+ env: opts.env,
365
+ json: (opts.format || '').toLowerCase() === 'json',
366
+ warningsAsErrors: opts.warningsAsErrors === true,
367
+ simulate: opts.simulate === true,
368
+ verbose: opts.verbose === true
369
+ },
370
+ logger
371
+ );
372
+ if (code !== 0) {
373
+ process.exit(code);
374
+ }
375
+ }
376
+
377
+ function buildValidateJsonPayload(result, appOrFile) {
378
+ let payload = result;
379
+ try {
380
+ if (result?.appPath && appOrFile && typeof appOrFile === 'string') {
381
+ if (!(fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile())) {
382
+ const { getManifestSourcePayload } = require('../utils/manifest-source-emit');
383
+ payload = { ...result, manifestSource: getManifestSourcePayload(appOrFile, result.appPath) };
384
+ }
373
385
  }
374
- if (!batchResult.valid) process.exit(1);
386
+ } catch {
387
+ /* keep plain result */
388
+ }
389
+ return payload;
390
+ }
391
+
392
+ function displaySingleValidateResult(appOrFile, result, outFormat, validate) {
393
+ emitManifestAfterValidateIfApplicable(appOrFile, result, outFormat);
394
+ if (outFormat === 'json') {
395
+ logger.log(JSON.stringify(buildValidateJsonPayload(result, appOrFile), null, 2));
375
396
  return;
376
397
  }
398
+ validate.displayValidationResults(result);
399
+ }
377
400
 
401
+ async function runValidateCommand(appOrFile, options) {
402
+ const validate = require('../validation/validate');
403
+ const opts = options.opts ? options.opts() : options;
404
+ const { isProtectionScope } = require('../protection/scope');
405
+ if (isProtectionScope(appOrFile)) {
406
+ await runProtectionValidateBranch(appOrFile, opts);
407
+ return;
408
+ }
409
+ if (await runValidateBatchBranch(validate, opts)) {
410
+ return;
411
+ }
412
+ const outFormat = (opts.format || 'default').toLowerCase();
378
413
  if (!appOrFile || typeof appOrFile !== 'string') {
379
414
  logger.log(chalk.red('App name or file path is required, or use --integration / --builder'));
380
415
  process.exit(1);
381
416
  }
382
-
383
417
  const result = await validate.validateAppOrFile(appOrFile, {
384
418
  ...opts,
385
419
  certSync: opts.certSync === true
386
420
  });
387
- if (outFormat === 'json') {
388
- logger.log(JSON.stringify(result, null, 2));
389
- } else {
390
- validate.displayValidationResults(result);
391
- }
421
+ displaySingleValidateResult(appOrFile, result, outFormat, validate);
392
422
  if (!result.valid) process.exit(1);
393
423
  }
394
424
 
395
425
  function setupValidateDiffCommands(program) {
396
- program.command('validate [appOrFile]')
397
- .description('Validate one app/file or all under integration/ or builder/')
426
+ program.command('validate [appKey|systemKey|file]')
427
+ .description('Validate appKey, systemKey, file path, .protection batch, or all under integration/ and builder/')
398
428
  .addHelpText('after', VALIDATE_HELP_AFTER)
399
429
  .option('--format <format>', 'Output format: json | default (human-readable)')
400
430
  .option('--integration', 'Validate all applications under integration/')
401
431
  .option('--builder', 'Validate all applications under builder/')
432
+ .option('--warnings-as-errors', 'For .protection: pass strict=true to dataplane validate')
433
+ .option('--simulate', 'For .protection: run simulate after validate')
402
434
  .option(
403
435
  '--cert-sync',
404
436
  'After successful validation of an external integration, refresh the system file certification block from the dataplane (requires login)'
@@ -15,6 +15,7 @@ const containerHelpers = require('../utils/app-run-containers');
15
15
  const { validateAppName } = require('../app/push');
16
16
 
17
17
  const { execWithDockerEnv } = require('../utils/docker-exec');
18
+ const { maskEnvLine } = require('../utils/log-redaction');
18
19
 
19
20
  /** Default number of log lines */
20
21
  const DEFAULT_TAIL_LINES = 100;
@@ -43,36 +44,11 @@ const LEVEL_JSON_NUMERIC_REGEX = /"level"\s*:\s*(\d+)/;
43
44
  /** Fallback: line contains whole-word "error" or "Error" when no other level detected (catches stack traces, "Error: msg", etc.) */
44
45
  const ERROR_WORD_FALLBACK_REGEX = /\berror\b/i;
45
46
 
46
- /** Env key patterns that indicate a secret (mask value) */
47
- const SECRET_KEY_PATTERN = /password|secret|token|credential|api[_-]?key/i;
47
+ /** Keycloak / Java style: validation failure without the word "error" (would be dropped by `-l error` otherwise) */
48
+ const VALIDATION_FAILURE_LINE_REGEX = /\bvalidation\s+failed\b/i;
48
49
 
49
- /** Prefixes to strip before checking key (avoids masking KEYCLOAK_SERVER_URL etc.) */
50
- const KEY_PREFIXES_TO_STRIP = /^KEYCLOAK_|^KEY_VAULT_/;
51
-
52
- /** URL with embedded credentials: scheme://user:password@host → scheme://user:***@host */
53
- const URL_CREDENTIAL_PATTERN = /(\w+:\/\/)([^:@]*):([^@]+)@/g;
54
-
55
- /**
56
- * Masks a single env line if the key looks like a secret or value contains URL credentials
57
- * @param {string} line - Line in form KEY=value
58
- * @returns {string} Same line or KEY=*** or value with masked URL credentials
59
- */
60
- function maskEnvLine(line) {
61
- const eq = line.indexOf('=');
62
- if (eq <= 0) return line;
63
- const key = line.slice(0, eq);
64
- const value = line.slice(eq + 1);
65
-
66
- const keyForCheck = key.replace(KEY_PREFIXES_TO_STRIP, '');
67
- const isSecretKey = SECRET_KEY_PATTERN.test(keyForCheck);
68
-
69
- const maskedValue = value.replace(URL_CREDENTIAL_PATTERN, '$1$2:***@');
70
- const hasUrlCredentials = maskedValue !== value;
71
-
72
- if (isSecretKey) return `${key}=***`;
73
- if (hasUrlCredentials) return `${key}=${maskedValue}`;
74
- return line;
75
- }
50
+ /** Other failure phrases treated as error severity for level filtering */
51
+ const FAILURE_PHRASE_AS_ERROR_REGEX = /\b(fatal|failed to run|unable to (start|run)|exception in thread)\b/i;
76
52
 
77
53
  /** Normalize level string to canonical 'debug'|'info'|'warn'|'error'. */
78
54
  function normalizeLevel(raw) {
@@ -104,9 +80,66 @@ function getLogLevel(line) {
104
80
  const jsonNum = line.match(LEVEL_JSON_NUMERIC_REGEX);
105
81
  if (jsonNum) return numericLevelToName(parseInt(jsonNum[1], 10));
106
82
  if (ERROR_WORD_FALLBACK_REGEX.test(line)) return 'error';
83
+ if (VALIDATION_FAILURE_LINE_REGEX.test(line)) return 'error';
84
+ if (FAILURE_PHRASE_AS_ERROR_REGEX.test(line)) return 'error';
85
+ return null;
86
+ }
87
+
88
+ /** Lines after an `error`-level line often have no level prefix (e.g. Keycloak config validation details). */
89
+ const ERROR_FILTER_CONTEXT_UNCLASSIFIED_MAX = 50;
90
+
91
+ /**
92
+ * Whether to show a log line when `--level` filtering is on. After an emitted error line, includes
93
+ * up to {@link ERROR_FILTER_CONTEXT_UNCLASSIFIED_MAX} following lines with no parseable level so
94
+ * stack traces and multi-line validation output are not dropped.
95
+ *
96
+ * @param {string} line
97
+ * @param {string|null} minLevel - normalized LOG_LEVELS value or null
98
+ * @param {{ pendingAfterError: number }} state - mutable; only used when minLevel is `error`
99
+ * @returns {boolean}
100
+ */
101
+ /**
102
+ * @param {string|null} level
103
+ * @param {string} minLevel
104
+ * @param {{ pendingAfterError: number }} state
105
+ * @returns {boolean|null} true if line shown by sticky rule; null to continue
106
+ */
107
+ function tryStickyErrorContinuation(level, minLevel, state) {
108
+ if (minLevel !== 'error' || state.pendingAfterError <= 0) {
109
+ return null;
110
+ }
111
+ if (level === null || level === undefined) {
112
+ state.pendingAfterError -= 1;
113
+ return true;
114
+ }
115
+ if (level !== 'error') {
116
+ state.pendingAfterError = 0;
117
+ }
107
118
  return null;
108
119
  }
109
120
 
121
+ function shouldShowFilteredLogLine(line, minLevel, state) {
122
+ if (minLevel === null || minLevel === undefined || minLevel === '' || !LOG_LEVELS.includes(minLevel)) {
123
+ return true;
124
+ }
125
+ if (line.trim() === '' && minLevel === 'error' && state.pendingAfterError > 0) {
126
+ return true;
127
+ }
128
+ const level = getLogLevel(line);
129
+ const sticky = tryStickyErrorContinuation(level, minLevel, state);
130
+ if (sticky === true) {
131
+ return true;
132
+ }
133
+
134
+ if (passesLevelFilter(level, minLevel)) {
135
+ if (minLevel === 'error' && level === 'error') {
136
+ state.pendingAfterError = ERROR_FILTER_CONTEXT_UNCLASSIFIED_MAX;
137
+ }
138
+ return true;
139
+ }
140
+ return false;
141
+ }
142
+
110
143
  /**
111
144
  * Whether a line's level passes the minimum level filter (show this level and above).
112
145
  * @param {string|null} lineLevel - Level from getLogLevel (null treated as 'info')
@@ -143,7 +176,12 @@ async function dumpMaskedEnv(containerName) {
143
176
  lines.forEach((line) => logger.log(maskEnvLine(line)));
144
177
  logger.log(chalk.gray('\n--- Logs ---\n'));
145
178
  } catch (err) {
146
- logger.log(chalk.gray('(Could not read container env; container may be stopped)\n'));
179
+ logger.log(
180
+ chalk.gray(
181
+ '(Could not read container env — the container may be stopped, restarting, or not ready yet. ' +
182
+ 'Docker log output below is still shown.)\n'
183
+ )
184
+ );
147
185
  }
148
186
  }
149
187
 
@@ -175,8 +213,9 @@ async function runDockerLogs(containerName, options) {
175
213
  const proc = spawn('docker', args, { stdio: ['inherit', 'pipe', 'pipe'], env: dockerEnv });
176
214
  proc.on('error', reject);
177
215
 
216
+ const filterState = { pendingAfterError: 0 };
178
217
  function onLine(line) {
179
- if (passesLevelFilter(getLogLevel(line), minLevel)) {
218
+ if (shouldShowFilteredLogLine(line, minLevel, filterState)) {
180
219
  process.stdout.write(line + '\n');
181
220
  }
182
221
  }
@@ -241,8 +280,11 @@ async function runDockerLogsFollow(containerName, tail, minLevel) {
241
280
  logger.log(chalk.red(`Error: ${err.message}`));
242
281
  process.exit(1);
243
282
  });
283
+ const filterState = { pendingAfterError: 0 };
244
284
  function onLine(line) {
245
- if (passesLevelFilter(getLogLevel(line), level)) process.stdout.write(line + '\n');
285
+ if (shouldShowFilteredLogLine(line, level, filterState)) {
286
+ process.stdout.write(line + '\n');
287
+ }
246
288
  }
247
289
  const rlOut = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
248
290
  rlOut.on('line', onLine);
@@ -301,4 +343,10 @@ async function runAppLogs(appKey, options = {}) {
301
343
  }
302
344
  }
303
345
 
304
- module.exports = { runAppLogs, maskEnvLine, getLogLevel, passesLevelFilter };
346
+ module.exports = {
347
+ runAppLogs,
348
+ maskEnvLine,
349
+ getLogLevel,
350
+ passesLevelFilter,
351
+ shouldShowFilteredLogLine
352
+ };