@aifabrix/builder 2.44.0 → 2.44.1

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 (66) hide show
  1. package/.cursor/rules/cli-layout.mdc +75 -0
  2. package/.cursor/rules/project-rules.mdc +8 -0
  3. package/.npmrc.token +1 -0
  4. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  5. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +1 -0
  6. package/.nyc_output/processinfo/index.json +1 -0
  7. package/jest.projects.js +15 -2
  8. package/lib/api/certificates.api.js +62 -0
  9. package/lib/api/index.js +11 -2
  10. package/lib/api/types/certificates.types.js +48 -0
  11. package/lib/api/validation-run.api.js +16 -4
  12. package/lib/api/validation-runner.js +13 -3
  13. package/lib/app/certification-show-enrich.js +129 -0
  14. package/lib/app/certification-verify-rows.js +60 -0
  15. package/lib/app/show-display.js +43 -0
  16. package/lib/app/show.js +92 -8
  17. package/lib/certification/cli-cert-sync-skip.js +21 -0
  18. package/lib/certification/merge-certification-from-artifact.js +185 -0
  19. package/lib/certification/post-unified-cert-sync.js +33 -0
  20. package/lib/certification/sync-after-external-command.js +52 -0
  21. package/lib/certification/sync-system-certification.js +197 -0
  22. package/lib/cli/setup-app.js +4 -0
  23. package/lib/cli/setup-app.test-commands.js +24 -8
  24. package/lib/cli/setup-external-system.js +22 -1
  25. package/lib/cli/setup-secrets.js +34 -13
  26. package/lib/cli/setup-utility.js +18 -2
  27. package/lib/commands/app.js +10 -1
  28. package/lib/commands/datasource-unified-test-cli.js +50 -117
  29. package/lib/commands/datasource-unified-test-cli.options.js +44 -2
  30. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +106 -0
  31. package/lib/commands/datasource-validation-cli.js +15 -1
  32. package/lib/commands/datasource.js +25 -2
  33. package/lib/commands/upload.js +17 -6
  34. package/lib/datasource/log-viewer.js +105 -14
  35. package/lib/datasource/test-e2e.js +35 -17
  36. package/lib/datasource/unified-validation-run-body.js +3 -0
  37. package/lib/datasource/unified-validation-run.js +2 -1
  38. package/lib/external-system/deploy.js +53 -18
  39. package/lib/infrastructure/compose.js +12 -3
  40. package/lib/infrastructure/helpers-docker-check.js +67 -0
  41. package/lib/infrastructure/helpers.js +47 -58
  42. package/lib/infrastructure/index.js +3 -1
  43. package/lib/infrastructure/services.js +4 -56
  44. package/lib/schema/external-system.schema.json +25 -3
  45. package/lib/schema/type/document-storage.json +15 -2
  46. package/lib/utils/api.js +28 -3
  47. package/lib/utils/configuration-env-resolver.js +11 -8
  48. package/lib/utils/credential-secrets-env.js +5 -5
  49. package/lib/utils/datasource-test-run-certificate-tty.js +82 -0
  50. package/lib/utils/datasource-test-run-display.js +19 -2
  51. package/lib/utils/datasource-test-run-exit.js +25 -0
  52. package/lib/utils/external-system-display.js +8 -0
  53. package/lib/utils/external-system-system-test-tty-overview.js +120 -0
  54. package/lib/utils/external-system-system-test-tty.js +417 -0
  55. package/lib/utils/paths.js +14 -0
  56. package/lib/utils/validation-run-poll.js +28 -5
  57. package/lib/utils/validation-run-post-retry.js +20 -8
  58. package/lib/utils/validation-run-request.js +18 -0
  59. package/lib/validation/validate-external-cert-sync.js +23 -0
  60. package/lib/validation/validate.js +4 -1
  61. package/package.json +4 -3
  62. package/scripts/install-local.js +4 -1
  63. package/scripts/pnpm-global-remove.js +48 -0
  64. package/templates/applications/dataplane/env.template +4 -0
  65. package/templates/infra/compose.yaml.hbs +15 -14
  66. package/templates/infra/servers.json.hbs +3 -1
@@ -4,6 +4,8 @@
4
4
  * @version 2.0.0
5
5
  */
6
6
 
7
+ const chalk = require('chalk');
8
+ const logger = require('./logger');
7
9
  const { getValidationRunWithTransportRetry } = require('./validation-run-post-retry');
8
10
 
9
11
  const INITIAL_INTERVAL_MS = 2000;
@@ -23,6 +25,19 @@ function sleep(ms) {
23
25
  return new Promise(resolve => setTimeout(resolve, ms));
24
26
  }
25
27
 
28
+ function maybeLogPollProgress(envelope, verbosePoll, lastProgressLogAtRef) {
29
+ if (!verbosePoll || !envelope || typeof envelope !== 'object') return;
30
+ const now = Date.now();
31
+ if (now - lastProgressLogAtRef[0] < 5000) return;
32
+ lastProgressLogAtRef[0] = now;
33
+ const st = envelope.status !== undefined && envelope.status !== null ? String(envelope.status) : '?';
34
+ const c =
35
+ envelope.reportCompleteness !== undefined && envelope.reportCompleteness !== null
36
+ ? String(envelope.reportCompleteness)
37
+ : '?';
38
+ logger.log(chalk.gray(` Polling validation run… completeness=${c} status=${st}`));
39
+ }
40
+
26
41
  /**
27
42
  * Whether polling should stop on this envelope.
28
43
  * @param {Object} envelope - DatasourceTestRun-like
@@ -42,6 +57,8 @@ function isTerminalReportCompleteness(envelope) {
42
57
  * @param {string} opts.testRunId
43
58
  * @param {number} opts.budgetMs - Remaining wall-clock budget for polls only (POST excluded)
44
59
  * @param {typeof getValidationRunWithTransportRetry} [opts.fetchRun] - Inject for tests (default: GET with transport retry)
60
+ * @param {boolean} [opts.verbosePoll] - Log throttled progress (plan §3.13)
61
+ * @param {number} [opts.pollRequestTimeoutMs] - Per-GET HTTP timeout (match validation aggregate budget)
45
62
  * @returns {Promise<{ envelope: Object|null, timedOut: boolean, lastApiResult: Object|null }>}
46
63
  */
47
64
  async function pollValidationRunUntilComplete(opts) {
@@ -50,18 +67,22 @@ async function pollValidationRunUntilComplete(opts) {
50
67
  authConfig,
51
68
  testRunId,
52
69
  budgetMs,
53
- fetchRun = getValidationRunWithTransportRetry
70
+ fetchRun = getValidationRunWithTransportRetry,
71
+ verbosePoll = false,
72
+ pollRequestTimeoutMs
54
73
  } = opts;
74
+ const pollTransportOpts =
75
+ Number.isFinite(pollRequestTimeoutMs) && pollRequestTimeoutMs > 0
76
+ ? { timeoutMs: pollRequestTimeoutMs }
77
+ : {};
55
78
  const deadline = Date.now() + Math.max(0, budgetMs);
56
79
  let attempt = 0;
57
80
  let lastApiResult = null;
58
81
  let envelope = null;
82
+ const lastProgressLogAtRef = [0];
59
83
 
60
84
  while (Date.now() < deadline) {
61
- const remaining = deadline - Date.now();
62
- if (remaining <= 0) break;
63
-
64
- lastApiResult = await fetchRun(dataplaneUrl, authConfig, testRunId);
85
+ lastApiResult = await fetchRun(dataplaneUrl, authConfig, testRunId, pollTransportOpts);
65
86
  if (!lastApiResult.success) {
66
87
  return { envelope: null, timedOut: false, lastApiResult };
67
88
  }
@@ -70,6 +91,8 @@ async function pollValidationRunUntilComplete(opts) {
70
91
  return { envelope, timedOut: false, lastApiResult };
71
92
  }
72
93
 
94
+ maybeLogPollProgress(envelope, verbosePoll, lastProgressLogAtRef);
95
+
73
96
  const delay = Math.min(nextPollDelayMs(attempt), Math.max(0, deadline - Date.now()));
74
97
  attempt += 1;
75
98
  if (delay > 0) {
@@ -33,18 +33,24 @@ function sleep(ms) {
33
33
  * @param {string} dataplaneUrl
34
34
  * @param {Object} authConfig
35
35
  * @param {Object} body
36
+ * @param {Object} [transportOpts] - forwarded to ``postValidationRun`` (e.g. ``timeoutMs``)
36
37
  * @returns {Promise<Object>}
37
38
  */
38
- async function postValidationRunWithTransportRetry(dataplaneUrl, authConfig, body) {
39
- let last = await postValidationRun(dataplaneUrl, authConfig, body);
39
+ async function postValidationRunWithTransportRetry(
40
+ dataplaneUrl,
41
+ authConfig,
42
+ body,
43
+ transportOpts = {}
44
+ ) {
45
+ let last = await postValidationRun(dataplaneUrl, authConfig, body, transportOpts);
40
46
  if (last.success || !isRetryablePostFailure(last)) return last;
41
47
 
42
48
  await sleep(1000);
43
- last = await postValidationRun(dataplaneUrl, authConfig, body);
49
+ last = await postValidationRun(dataplaneUrl, authConfig, body, transportOpts);
44
50
  if (last.success || !isRetryablePostFailure(last)) return last;
45
51
 
46
52
  await sleep(2000);
47
- return postValidationRun(dataplaneUrl, authConfig, body);
53
+ return postValidationRun(dataplaneUrl, authConfig, body, transportOpts);
48
54
  }
49
55
 
50
56
  /**
@@ -52,18 +58,24 @@ async function postValidationRunWithTransportRetry(dataplaneUrl, authConfig, bod
52
58
  * @param {string} dataplaneUrl
53
59
  * @param {Object} authConfig
54
60
  * @param {string} testRunId
61
+ * @param {Object} [transportOpts] - forwarded to ``getValidationRun`` (e.g. ``timeoutMs``)
55
62
  * @returns {Promise<Object>}
56
63
  */
57
- async function getValidationRunWithTransportRetry(dataplaneUrl, authConfig, testRunId) {
58
- let last = await getValidationRun(dataplaneUrl, authConfig, testRunId);
64
+ async function getValidationRunWithTransportRetry(
65
+ dataplaneUrl,
66
+ authConfig,
67
+ testRunId,
68
+ transportOpts = {}
69
+ ) {
70
+ let last = await getValidationRun(dataplaneUrl, authConfig, testRunId, transportOpts);
59
71
  if (last.success || !isRetryablePostFailure(last)) return last;
60
72
 
61
73
  await sleep(1000);
62
- last = await getValidationRun(dataplaneUrl, authConfig, testRunId);
74
+ last = await getValidationRun(dataplaneUrl, authConfig, testRunId, transportOpts);
63
75
  if (last.success || !isRetryablePostFailure(last)) return last;
64
76
 
65
77
  await sleep(2000);
66
- return getValidationRun(dataplaneUrl, authConfig, testRunId);
78
+ return getValidationRun(dataplaneUrl, authConfig, testRunId, transportOpts);
67
79
  }
68
80
 
69
81
  module.exports = {
@@ -16,6 +16,21 @@ function includeDebugForRequest(debugOpt) {
16
16
  return true;
17
17
  }
18
18
 
19
+ /**
20
+ * @param {Object} e2e
21
+ * @param {string} key
22
+ * @param {*} raw
23
+ */
24
+ function assignOptionalNonNegativeInt(e2e, key, raw) {
25
+ if (raw === undefined || raw === null || raw === '') {
26
+ return;
27
+ }
28
+ const n = Number(raw);
29
+ if (Number.isFinite(n) && n >= 0) {
30
+ e2e[key] = n;
31
+ }
32
+ }
33
+
19
34
  /**
20
35
  * Merge E2E options from CLI flags into e2eOptions for ExternalDataSourceE2ETestRequest (dataplane).
21
36
  * @param {Object} options - CLI-derived options
@@ -41,6 +56,9 @@ function buildE2eOptionsFromCli(options = {}) {
41
56
  if (options.primaryKeyValue !== undefined && options.primaryKeyValue !== null) {
42
57
  e2e.primaryKeyValue = options.primaryKeyValue;
43
58
  }
59
+ assignOptionalNonNegativeInt(e2e, 'minVectorHits', options.minVectorHits);
60
+ assignOptionalNonNegativeInt(e2e, 'minProcessed', options.minProcessed);
61
+ assignOptionalNonNegativeInt(e2e, 'minRecordCount', options.minRecordCount);
44
62
  if (options.e2eOptionsExtra && typeof options.e2eOptionsExtra === 'object') {
45
63
  Object.assign(e2e, options.e2eOptionsExtra);
46
64
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Optional certification sync after external validate (keeps validate.js under max-lines).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {string} appName
11
+ * @param {Object} options
12
+ * @param {function(string, Object): Promise<Object>} validateExternalSystemComplete
13
+ */
14
+ async function runExternalValidateWithOptionalCertSync(appName, options, validateExternalSystemComplete) {
15
+ const result = await validateExternalSystemComplete(appName, options);
16
+ if (result.valid && options.certSync === true) {
17
+ const { trySyncCertificationFromDataplaneForExternalApp } = require('../certification/sync-after-external-command');
18
+ await trySyncCertificationFromDataplaneForExternalApp(appName, 'validate');
19
+ }
20
+ return result;
21
+ }
22
+
23
+ module.exports = { runExternalValidateWithOptionalCertSync };
@@ -28,6 +28,7 @@ const {
28
28
  validateOAuth2GrantTypeAndAuthorizationUrl,
29
29
  validateConfigurationNoStandardAuthVariables
30
30
  } = require('./external-system-auth-rules');
31
+ const { runExternalValidateWithOptionalCertSync } = require('./validate-external-cert-sync');
31
32
 
32
33
  /**
33
34
  * Validates a file path (detects type and validates)
@@ -462,7 +463,9 @@ async function validateAppOrFile(appOrFile, options = {}) {
462
463
  const { appPath, isExternal } = await detectAppType(appName);
463
464
  logOfflinePathWhenType(appPath);
464
465
 
465
- if (isExternal) return await validateExternalSystemComplete(appName, options);
466
+ if (isExternal) {
467
+ return await runExternalValidateWithOptionalCertSync(appName, options, validateExternalSystemComplete);
468
+ }
466
469
 
467
470
  const appValidation = await validator.validateApplication(appName, options);
468
471
  const rbacValidation = await validateRbacForExternalSystem(isExternal, appName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.44.0",
3
+ "version": "2.44.1",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "lint:ci": "eslint . --ext .js --format json --output-file eslint-report.json",
30
30
  "dev": "node bin/aifabrix.js",
31
31
  "build": "npm run lint && npm run test",
32
- "build:ci": "npm run lint && npm run test:ci",
32
+ "build:ci": "npm run lint && npm run check:schema-sync && npm run check:flags && npm run test:ci",
33
33
  "pack": "npm run build && npm pack",
34
34
  "validate": "npm run build",
35
35
  "prepublishOnly": "npm run validate",
@@ -37,7 +37,8 @@
37
37
  "install:local": "node scripts/install-local.js && which aifabrix && which af",
38
38
  "uninstall:local": "node scripts/install-local.js uninstall && which aifabrix && which af",
39
39
  "diagnose:cli": "node scripts/diagnose-cli.js",
40
- "check:schema-sync": "node scripts/check-datasource-test-run-schema-sync.js"
40
+ "check:schema-sync": "node scripts/check-datasource-test-run-schema-sync.js",
41
+ "check:flags": "jest tests/lib/schema/flag-map-validation-run.test.js --runInBand --config jest.config.default.js"
41
42
  },
42
43
  "keywords": [
43
44
  "aifabrix",
@@ -15,6 +15,8 @@ const fs = require('fs');
15
15
  const os = require('os');
16
16
  const path = require('path');
17
17
 
18
+ const { runPnpmGlobalRemove } = require('./pnpm-global-remove');
19
+
18
20
  const PACKAGE_NAME = '@aifabrix/builder';
19
21
  /** Primary CLI name used for “current version” before link */
20
22
  const PRIMARY_BIN = 'aifabrix';
@@ -465,7 +467,8 @@ function uninstallLocal() {
465
467
 
466
468
  try {
467
469
  if (pm === 'pnpm') {
468
- execSync(`pnpm unlink --global ${PACKAGE_NAME}`, { stdio: 'inherit', env: pnpmEnv() });
470
+ const env = { ...pnpmEnv(), CI: 'true' };
471
+ runPnpmGlobalRemove(env, PACKAGE_NAME);
469
472
  displayUninstallSuccess(pm, currentVersion);
470
473
  } else {
471
474
  execSync(`npm unlink -g ${PACKAGE_NAME}`, { stdio: 'inherit' });
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+
4
+ /**
5
+ * pnpm global remove with virtual-store repair retry (pnpm 9+ global layout).
6
+ * @fileoverview Used by install-local.js uninstall path
7
+ * @author AI Fabrix Team
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+
12
+ /**
13
+ * @param {unknown} error - Thrown from execSync
14
+ * @returns {boolean} True when `pnpm i -g` repair may fix the failure
15
+ */
16
+ function isPnpmUnexpectedVirtualStoreError(error) {
17
+ const msg =
18
+ error && typeof error === 'object' && 'message' in error ? String(error.message) : String(error);
19
+ return msg.includes('ERR_PNPM_UNEXPECTED_VIRTUAL_STORE');
20
+ }
21
+
22
+ /**
23
+ * @param {NodeJS.ProcessEnv} env - Environment
24
+ * @returns {void} Nothing
25
+ */
26
+ function repairPnpmGlobalInstall(env) {
27
+ execSync('pnpm i -g', { stdio: 'inherit', env });
28
+ }
29
+
30
+ /**
31
+ * @param {NodeJS.ProcessEnv} env - pnpm env (e.g. pnpmEnv + CI)
32
+ * @param {string} packageName - Scoped package name to remove globally
33
+ * @returns {void} Nothing
34
+ */
35
+ function runPnpmGlobalRemove(env, packageName) {
36
+ try {
37
+ execSync(`pnpm remove -g ${packageName}`, { stdio: 'inherit', env });
38
+ } catch (firstError) {
39
+ if (!isPnpmUnexpectedVirtualStoreError(firstError)) throw firstError;
40
+ console.log(
41
+ '\n⚠️ pnpm global virtual store layout mismatch. Running `pnpm i -g` to repair, then retrying remove...\n'
42
+ );
43
+ repairPnpmGlobalInstall(env);
44
+ execSync(`pnpm remove -g ${packageName}`, { stdio: 'inherit', env });
45
+ }
46
+ }
47
+
48
+ module.exports = { runPnpmGlobalRemove };
@@ -185,6 +185,10 @@ CERTIFICATE_PUBLIC_KEY=
185
185
  # Optional public key identifier for issued certificates; default if unset: dataplane-signing-key
186
186
  CERTIFICATE_PUBLIC_KEY_ID=
187
187
 
188
+ # After a successful POST /api/v1/validation/run (validation engine with certification passed, or E2E success),
189
+ # persist an active integration certificate without a separate issue call. Set false to require explicit issue only.
190
+ VALIDATION_AUTO_ISSUE_INTEGRATION_CERTIFICATE=true
191
+
188
192
  # =============================================================================
189
193
  # CIP EXECUTION CONFIGURATION
190
194
  # =============================================================================
@@ -56,28 +56,29 @@ services:
56
56
  PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
57
57
  PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
58
58
  PGADMIN_CONFIG_SERVER_MODE: 'False'
59
+ # Docker has no OS password store; without this pgAdmin prompts for a master password on each restart.
60
+ PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
61
+ # load-servers runs without --user in desktop mode; servers attach to DESKTOP_USER (default pgadmin4@pgadmin.org).
62
+ # Must match PGADMIN_DEFAULT_EMAIL or the tree stays empty after login.
63
+ PGADMIN_CONFIG_DESKTOP_USER: "'${PGADMIN_DEFAULT_EMAIL}'"
59
64
  PGADMIN_SERVER_JSON_FILE: /pgadmin4/servers.json
60
65
  PGADMIN_REPLACE_SERVERS_ON_STARTUP: 'True'
61
- PGPASS_FILE: /pgpass
66
+ PGPASS_FILE: /pgadmin4/bootstrap.pgpass
62
67
  ports:
63
68
  - "{{pgadminPort}}:80"
64
69
  volumes:
65
70
  - {{#if (eq devId 0)}}pgadmin_data{{else}}dev{{devId}}_pgadmin_data{{/if}}:/var/lib/pgadmin
71
+ # File binds under /pgadmin4 (image paths). pgpass must exist before first entrypoint run.
66
72
  - type: bind
67
- source: "{{infraDirBind}}"
68
- target: /host-config
73
+ source: "{{serversJsonBind}}"
74
+ target: /pgadmin4/servers.json
69
75
  read_only: true
70
- command: >
71
- sh -c "
72
- if [ -f /host-config/servers.json ]; then
73
- cp /host-config/servers.json /pgadmin4/servers.json;
74
- fi &&
75
- if [ -f /host-config/pgpass ]; then
76
- cp /host-config/pgpass /pgpass;
77
- chmod 600 /pgpass;
78
- fi &&
79
- /entrypoint.sh
80
- "
76
+ {{#if pgadmin.pgpassBootstrapBind}}
77
+ - type: bind
78
+ source: "{{pgadmin.pgpassBootstrapBind}}"
79
+ target: /pgadmin4/bootstrap.pgpass
80
+ read_only: true
81
+ {{/if}}
81
82
  restart: unless-stopped
82
83
  depends_on:
83
84
  postgres:
@@ -7,8 +7,10 @@
7
7
  "Port": 5432,
8
8
  "MaintenanceDB": "postgres",
9
9
  "Username": "pgadmin",
10
- "PassFile": "/pgpass",
11
10
  "SSLMode": "prefer",
11
+ "ConnectionParameters": {
12
+ "passfile": "/pgadmin4/bootstrap.pgpass"
13
+ },
12
14
  "Comment": "Auto-registered PostgreSQL server with pgvector extension"
13
15
  }
14
16
  }