@aifabrix/builder 2.42.1 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/aifabrix.js +1 -1
- package/integration/hubspot-test/README.md +126 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +5 -5
- package/integration/hubspot-test/env.template +4 -0
- package/integration/{hubspot/hubspot-datasource-company.json → hubspot-test/hubspot-test-datasource-company.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-contact.json → hubspot-test/hubspot-test-datasource-contact.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-deal.json → hubspot-test/hubspot-test-datasource-deal.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-users.json → hubspot-test/hubspot-test-datasource-users.json} +3 -2
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +198 -21
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test.js +102 -59
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/lib/api/external-test.api.js +1 -1
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/app/register.js +3 -1
- package/lib/app/rotate-secret.js +3 -0
- package/lib/cli/setup-app.js +2 -2
- package/lib/cli/setup-auth.js +19 -11
- package/lib/cli/setup-dev.js +62 -32
- package/lib/cli/setup-environment.js +6 -21
- package/lib/cli/setup-infra.js +13 -0
- package/lib/cli/setup-secrets.js +45 -6
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +12 -0
- package/lib/commands/auth-config.js +4 -8
- package/lib/commands/datasource.js +46 -1
- package/lib/commands/dev-init.js +1 -1
- package/lib/commands/repair-env-template.js +14 -8
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +96 -30
- package/lib/commands/secrets-remove.js +1 -1
- package/lib/commands/secrets-validate.js +17 -4
- package/lib/commands/service-user.js +231 -2
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/core/admin-secrets.js +2 -0
- package/lib/core/config.js +7 -5
- package/lib/core/ensure-encryption-key.js +1 -3
- package/lib/core/secrets.js +32 -9
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +157 -0
- package/lib/datasource/field-reference-validator.js +74 -36
- package/lib/datasource/log-viewer.js +221 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +11 -20
- package/lib/datasource/test-integration.js +42 -22
- package/lib/datasource/validate.js +5 -2
- package/lib/external-system/generator.js +12 -8
- package/lib/external-system/test-system-level.js +1 -1
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external.js +7 -7
- package/lib/generator/helpers.js +13 -9
- package/lib/generator/index.js +4 -4
- package/lib/generator/split.js +45 -10
- package/lib/generator/wizard.js +9 -6
- package/lib/infrastructure/helpers.js +50 -35
- package/lib/infrastructure/index.js +39 -23
- package/lib/schema/env-config.yaml +19 -2
- package/lib/schema/external-datasource.schema.json +11 -1
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/config-paths.js +48 -4
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/env-map.js +7 -3
- package/lib/utils/error-formatter.js +37 -0
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-system-display.js +43 -0
- package/lib/utils/external-system-validators.js +2 -2
- package/lib/utils/help-builder.js +3 -5
- package/lib/utils/local-secrets.js +26 -3
- package/lib/utils/paths.js +2 -1
- package/lib/utils/secrets-generator.js +2 -2
- package/lib/utils/secrets-utils.js +4 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/token-manager.js +36 -3
- package/lib/utils/yaml-preserve.js +59 -1
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +8 -0
- package/lib/validation/validate.js +8 -0
- package/lib/validation/validator.js +10 -13
- package/package.json +5 -1
- package/templates/applications/dataplane/env.template +5 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -2
- package/templates/external-system/env.template.hbs +22 -0
- package/integration/hubspot/README.md +0 -102
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +0 -0
|
@@ -23,8 +23,8 @@ const { resolveEnvironment } = require('../../lib/core/config');
|
|
|
23
23
|
|
|
24
24
|
const execFileAsync = promisify(execFile);
|
|
25
25
|
|
|
26
|
-
/** Single source for test config: integration/hubspot/.env */
|
|
27
|
-
const HUBSPOT_DIR = path.join(process.cwd(), 'integration', 'hubspot');
|
|
26
|
+
/** Single source for test config: integration/hubspot-test/.env */
|
|
27
|
+
const HUBSPOT_DIR = path.join(process.cwd(), 'integration', 'hubspot-test');
|
|
28
28
|
const LOCAL_ENV_PATH = path.join(HUBSPOT_DIR, '.env');
|
|
29
29
|
const ARTIFACT_DIR = path.join(HUBSPOT_DIR, 'test-artifacts');
|
|
30
30
|
const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
@@ -96,10 +96,10 @@ function printUsage() {
|
|
|
96
96
|
// eslint-disable-next-line no-console
|
|
97
97
|
console.log([
|
|
98
98
|
'Usage:',
|
|
99
|
-
' node integration/hubspot/test.js',
|
|
100
|
-
' node integration/hubspot/test.js --test "1.1"',
|
|
101
|
-
' node integration/hubspot/test.js --type positive',
|
|
102
|
-
' node integration/hubspot/test.js --type negative --verbose',
|
|
99
|
+
' node integration/hubspot-test/test.js',
|
|
100
|
+
' node integration/hubspot-test/test.js --test "1.1"',
|
|
101
|
+
' node integration/hubspot-test/test.js --type positive',
|
|
102
|
+
' node integration/hubspot-test/test.js --type negative --verbose',
|
|
103
103
|
'',
|
|
104
104
|
'Options:',
|
|
105
105
|
' --test <id[,id]> Run specific test IDs',
|
|
@@ -266,7 +266,7 @@ async function loadEnvFile(envPath, options) {
|
|
|
266
266
|
process.env[key] = value;
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
// Map common .env keys so tests and wizard use credentials from integration/hubspot/.env
|
|
269
|
+
// Map common .env keys so tests and wizard use credentials from integration/hubspot-test/.env
|
|
270
270
|
if (process.env.CLIENTID && process.env.HUBSPOT_CLIENT_ID === undefined) {
|
|
271
271
|
process.env.HUBSPOT_CLIENT_ID = process.env.CLIENTID;
|
|
272
272
|
}
|
|
@@ -280,7 +280,7 @@ async function loadEnvFile(envPath, options) {
|
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
282
|
* Load test config (controller, environment, dataplane, openapi file).
|
|
283
|
-
* Reads integration/hubspot/.env; missing CONTROLLER_URL/ENVIRONMENT fall back to
|
|
283
|
+
* Reads integration/hubspot-test/.env; missing CONTROLLER_URL/ENVIRONMENT fall back to
|
|
284
284
|
* the same resolution as the CLI (af auth status) so tests use the same controller.
|
|
285
285
|
* @async
|
|
286
286
|
* @function loadTestConfigFromEnv
|
|
@@ -502,7 +502,20 @@ async function checkAppDirectory(appPath) {
|
|
|
502
502
|
}
|
|
503
503
|
|
|
504
504
|
/**
|
|
505
|
-
*
|
|
505
|
+
* Resolves application config path (application.json or application.yaml)
|
|
506
|
+
* @param {string} appPath - Application directory path
|
|
507
|
+
* @returns {string|null} Path to config file or null if neither exists
|
|
508
|
+
*/
|
|
509
|
+
async function resolveApplicationConfigPath(appPath) {
|
|
510
|
+
const jsonPath = path.join(appPath, 'application.json');
|
|
511
|
+
const yamlPath = path.join(appPath, 'application.yaml');
|
|
512
|
+
if (await fileExists(jsonPath)) return jsonPath;
|
|
513
|
+
if (await fileExists(yamlPath)) return yamlPath;
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Validates required files exist (application config can be application.json or application.yaml)
|
|
506
519
|
* @async
|
|
507
520
|
* @function validateRequiredFiles
|
|
508
521
|
* @param {string} appPath - Application directory path
|
|
@@ -511,8 +524,12 @@ async function checkAppDirectory(appPath) {
|
|
|
511
524
|
* @throws {Error} If required files are missing
|
|
512
525
|
*/
|
|
513
526
|
async function validateRequiredFiles(appPath, entries) {
|
|
514
|
-
const
|
|
527
|
+
const applicationConfigPath = await resolveApplicationConfigPath(appPath);
|
|
528
|
+
const requiredFiles = ['env.template', 'README.md', 'deploy.js'];
|
|
515
529
|
const missingFiles = [];
|
|
530
|
+
if (!applicationConfigPath) {
|
|
531
|
+
missingFiles.push('application.yaml or application.json');
|
|
532
|
+
}
|
|
516
533
|
for (const fileName of requiredFiles) {
|
|
517
534
|
const filePath = path.join(appPath, fileName);
|
|
518
535
|
const exists = await fileExists(filePath);
|
|
@@ -557,11 +574,19 @@ function validateDeployFiles(appPath, entries) {
|
|
|
557
574
|
* @throws {Error} If file contents are invalid
|
|
558
575
|
*/
|
|
559
576
|
async function validateFileContents(appPath, deployFiles) {
|
|
577
|
+
const configPath = await resolveApplicationConfigPath(appPath);
|
|
578
|
+
if (!configPath) {
|
|
579
|
+
throw new Error('Application config (application.yaml or application.json) not found');
|
|
580
|
+
}
|
|
560
581
|
try {
|
|
561
|
-
const
|
|
562
|
-
|
|
582
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
583
|
+
if (configPath.endsWith('.json')) {
|
|
584
|
+
JSON.parse(content);
|
|
585
|
+
} else {
|
|
586
|
+
yaml.load(content);
|
|
587
|
+
}
|
|
563
588
|
} catch (error) {
|
|
564
|
-
throw new Error(`Invalid
|
|
589
|
+
throw new Error(`Invalid syntax in ${path.basename(configPath)}: ${error.message}`);
|
|
565
590
|
}
|
|
566
591
|
for (const fileName of deployFiles) {
|
|
567
592
|
try {
|
|
@@ -597,22 +622,23 @@ async function validateGeneratedFiles(appName) {
|
|
|
597
622
|
* @returns {Promise<Object>} Snapshot of file contents keyed by path
|
|
598
623
|
*/
|
|
599
624
|
async function captureExternalSnapshot(appPath) {
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
625
|
+
const configPath = await resolveApplicationConfigPath(appPath);
|
|
626
|
+
if (!configPath) {
|
|
627
|
+
throw new Error(`Application config not found in ${appPath}`);
|
|
628
|
+
}
|
|
629
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
630
|
+
const variables = configPath.endsWith('.json') ? JSON.parse(content) : yaml.load(content);
|
|
603
631
|
|
|
604
632
|
if (!variables || !variables.externalIntegration) {
|
|
605
|
-
throw new Error(`externalIntegration block not found in ${
|
|
633
|
+
throw new Error(`externalIntegration block not found in ${configPath}`);
|
|
606
634
|
}
|
|
607
635
|
|
|
608
|
-
const systemFiles = variables.externalIntegration.systems || [];
|
|
609
|
-
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
610
636
|
const fileNames = [
|
|
611
|
-
|
|
637
|
+
path.basename(configPath),
|
|
612
638
|
'env.template',
|
|
613
639
|
'README.md',
|
|
614
|
-
...
|
|
615
|
-
...
|
|
640
|
+
...(variables.externalIntegration.systems || []),
|
|
641
|
+
...(variables.externalIntegration.dataSources || [])
|
|
616
642
|
];
|
|
617
643
|
|
|
618
644
|
const rbacPath = path.join(appPath, 'rbac.yml');
|
|
@@ -658,7 +684,7 @@ function compareSnapshots(before, after) {
|
|
|
658
684
|
* @returns {boolean} True if test app name
|
|
659
685
|
*/
|
|
660
686
|
function isTestAppName(appName) {
|
|
661
|
-
return appName.startsWith('
|
|
687
|
+
return appName.startsWith('wizard-e2e-');
|
|
662
688
|
}
|
|
663
689
|
|
|
664
690
|
/**
|
|
@@ -769,6 +795,20 @@ async function testDownloadAndSplit(appName, context, options) {
|
|
|
769
795
|
logSuccess('Download and split workflow validated.');
|
|
770
796
|
}
|
|
771
797
|
|
|
798
|
+
/**
|
|
799
|
+
* Returns a skip message only when the wizard failure is due to dataplane unreachable (no service).
|
|
800
|
+
* Does not skip for auth/session errors when dataplane is up (so tests fail with the real error).
|
|
801
|
+
* @param {string} errorOutput - Combined stdout + stderr from wizard command
|
|
802
|
+
* @returns {string|null} Skip message or null
|
|
803
|
+
*/
|
|
804
|
+
function getWizardEnvironmentSkipMessage(errorOutput) {
|
|
805
|
+
if (errorOutput.includes('Failed to discover dataplane URL') ||
|
|
806
|
+
errorOutput.includes('Application not found')) {
|
|
807
|
+
return 'Dataplane service not found in environment. Deploy dataplane service to the controller.';
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
|
|
772
812
|
/**
|
|
773
813
|
* Runs wizard and validates generated files
|
|
774
814
|
* @async
|
|
@@ -778,12 +818,17 @@ async function testDownloadAndSplit(appName, context, options) {
|
|
|
778
818
|
* @param {Object} context - Test context
|
|
779
819
|
* @param {Object} options - Options object
|
|
780
820
|
* @returns {Promise<void>} Resolves when wizard completes and files are validated
|
|
821
|
+
* @throws {SkipTestError} If wizard fails due to environment (dataplane/auth)
|
|
781
822
|
* @throws {Error} If wizard fails or validation fails
|
|
782
823
|
*/
|
|
783
824
|
async function runWizardAndValidate(configPath, appName, context, options) {
|
|
784
825
|
const result = await runWizard(configPath, context, options);
|
|
785
826
|
if (!result.success) {
|
|
786
827
|
const errorOutput = `${result.stdout}\n${result.stderr}`;
|
|
828
|
+
const skipMsg = getWizardEnvironmentSkipMessage(errorOutput);
|
|
829
|
+
if (skipMsg) {
|
|
830
|
+
throw new SkipTestError(skipMsg);
|
|
831
|
+
}
|
|
787
832
|
throw new Error(`Wizard failed for ${appName}:\n${errorOutput}`);
|
|
788
833
|
}
|
|
789
834
|
|
|
@@ -884,18 +929,18 @@ function buildPositiveTestCases(context) {
|
|
|
884
929
|
throw new Error(`OpenAPI file not found: ${context.openapiFile}`);
|
|
885
930
|
}
|
|
886
931
|
ensureEnvVar('CONTROLLER_URL', context.controllerUrl);
|
|
887
|
-
// Try to run wizard - if dataplane
|
|
932
|
+
// Try to run wizard - if dataplane/auth fails, skip the test
|
|
888
933
|
const result = await runWizard(configPath, context, options);
|
|
889
934
|
if (!result.success) {
|
|
890
935
|
const errorOutput = `${result.stdout}\n${result.stderr}`;
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
throw new SkipTestError(
|
|
936
|
+
const skipMsg = getWizardEnvironmentSkipMessage(errorOutput);
|
|
937
|
+
if (skipMsg) {
|
|
938
|
+
throw new SkipTestError(skipMsg);
|
|
894
939
|
}
|
|
895
940
|
throw new Error(`Wizard failed: ${errorOutput}`);
|
|
896
941
|
}
|
|
897
|
-
await validateGeneratedFiles('
|
|
898
|
-
await cleanupAppArtifacts('
|
|
942
|
+
await validateGeneratedFiles('wizard-e2e-e2e');
|
|
943
|
+
await cleanupAppArtifacts('wizard-e2e-e2e', options);
|
|
899
944
|
}
|
|
900
945
|
},
|
|
901
946
|
{
|
|
@@ -908,11 +953,11 @@ function buildPositiveTestCases(context) {
|
|
|
908
953
|
throw new Error(`Missing config file: ${configPath}`);
|
|
909
954
|
}
|
|
910
955
|
try {
|
|
911
|
-
await ensureDataplaneUrl(context, '
|
|
956
|
+
await ensureDataplaneUrl(context, 'wizard-e2e-platform');
|
|
912
957
|
} catch (error) {
|
|
913
958
|
throw new SkipTestError(`Dataplane service not found: ${error.message}`);
|
|
914
959
|
}
|
|
915
|
-
await runWizardAndValidate(configPath, '
|
|
960
|
+
await runWizardAndValidate(configPath, 'wizard-e2e-platform', context, options);
|
|
916
961
|
}
|
|
917
962
|
},
|
|
918
963
|
{
|
|
@@ -922,14 +967,14 @@ function buildPositiveTestCases(context) {
|
|
|
922
967
|
run: async(options) => {
|
|
923
968
|
let dataplaneUrl;
|
|
924
969
|
try {
|
|
925
|
-
dataplaneUrl = await ensureDataplaneUrl(context, '
|
|
970
|
+
dataplaneUrl = await ensureDataplaneUrl(context, 'wizard-e2e-env-vars');
|
|
926
971
|
} catch (error) {
|
|
927
972
|
throw new SkipTestError(`Dataplane service not found: ${error.message}`);
|
|
928
973
|
}
|
|
929
974
|
ensureEnvVar('CONTROLLER_URL', context.controllerUrl);
|
|
930
975
|
ensureEnvVar('DATAPLANE_URL', dataplaneUrl);
|
|
931
976
|
const configPath = await writeWizardConfig('wizard-hubspot-env-vars', {
|
|
932
|
-
appName: '
|
|
977
|
+
appName: 'wizard-e2e-env-vars',
|
|
933
978
|
mode: 'create-system',
|
|
934
979
|
source: {
|
|
935
980
|
type: 'openapi-file',
|
|
@@ -941,7 +986,7 @@ function buildPositiveTestCases(context) {
|
|
|
941
986
|
environment: context.environment
|
|
942
987
|
}
|
|
943
988
|
});
|
|
944
|
-
await runWizardAndValidate(configPath, '
|
|
989
|
+
await runWizardAndValidate(configPath, 'wizard-e2e-env-vars', context, options);
|
|
945
990
|
}
|
|
946
991
|
}
|
|
947
992
|
];
|
|
@@ -966,7 +1011,7 @@ function buildRealDataTestCases(context) {
|
|
|
966
1011
|
requireEnvVars(['HUBSPOT_CLIENT_ID', 'HUBSPOT_CLIENT_SECRET']);
|
|
967
1012
|
ensureEnvVar('HUBSPOT_TOKEN_URL', 'https://api.hubapi.com/oauth/v1/token');
|
|
968
1013
|
const configPath = await writeWizardConfig('wizard-hubspot-credential-real', {
|
|
969
|
-
appName: '
|
|
1014
|
+
appName: 'wizard-e2e-credential-real',
|
|
970
1015
|
mode: 'create-system',
|
|
971
1016
|
source: {
|
|
972
1017
|
type: 'openapi-file',
|
|
@@ -975,7 +1020,7 @@ function buildRealDataTestCases(context) {
|
|
|
975
1020
|
credential: {
|
|
976
1021
|
action: 'create',
|
|
977
1022
|
config: {
|
|
978
|
-
key: '
|
|
1023
|
+
key: 'wizard-e2e-cred-real',
|
|
979
1024
|
displayName: 'HubSpot Test Credential (Real)',
|
|
980
1025
|
type: 'OAUTH2',
|
|
981
1026
|
config: {
|
|
@@ -992,7 +1037,7 @@ function buildRealDataTestCases(context) {
|
|
|
992
1037
|
}
|
|
993
1038
|
}
|
|
994
1039
|
});
|
|
995
|
-
await runWizardAndValidate(configPath, '
|
|
1040
|
+
await runWizardAndValidate(configPath, 'wizard-e2e-credential-real', context, options);
|
|
996
1041
|
}
|
|
997
1042
|
}
|
|
998
1043
|
];
|
|
@@ -1038,7 +1083,7 @@ function buildNegativeConfigTestCases(context) {
|
|
|
1038
1083
|
name: 'Missing source block',
|
|
1039
1084
|
run: async(options) => {
|
|
1040
1085
|
const configPath = await writeWizardConfig('wizard-invalid-missing-source', {
|
|
1041
|
-
appName: '
|
|
1086
|
+
appName: 'wizard-e2e-negative-missing-source',
|
|
1042
1087
|
mode: 'create-system'
|
|
1043
1088
|
});
|
|
1044
1089
|
await runWizardExpectFailure(configPath, context, options, 'Missing required field: source');
|
|
@@ -1050,7 +1095,7 @@ function buildNegativeConfigTestCases(context) {
|
|
|
1050
1095
|
name: 'Invalid source type',
|
|
1051
1096
|
run: async(options) => {
|
|
1052
1097
|
const configPath = await writeWizardConfig('wizard-invalid-source', {
|
|
1053
|
-
appName: '
|
|
1098
|
+
appName: 'wizard-e2e-negative-source',
|
|
1054
1099
|
mode: 'create-system',
|
|
1055
1100
|
source: { type: 'invalid-type' }
|
|
1056
1101
|
});
|
|
@@ -1063,7 +1108,7 @@ function buildNegativeConfigTestCases(context) {
|
|
|
1063
1108
|
name: 'Invalid mode',
|
|
1064
1109
|
run: async(options) => {
|
|
1065
1110
|
const configPath = await writeWizardConfig('wizard-invalid-mode', {
|
|
1066
|
-
appName: '
|
|
1111
|
+
appName: 'wizard-e2e-negative-mode',
|
|
1067
1112
|
mode: 'invalid-mode',
|
|
1068
1113
|
source: { type: 'known-platform', platform: 'hubspot' }
|
|
1069
1114
|
});
|
|
@@ -1076,7 +1121,7 @@ function buildNegativeConfigTestCases(context) {
|
|
|
1076
1121
|
name: 'Known platform missing platform',
|
|
1077
1122
|
run: async(options) => {
|
|
1078
1123
|
const configPath = await writeWizardConfig('wizard-invalid-known-platform', {
|
|
1079
|
-
appName: '
|
|
1124
|
+
appName: 'wizard-e2e-negative-platform',
|
|
1080
1125
|
mode: 'create-system',
|
|
1081
1126
|
source: { type: 'known-platform' }
|
|
1082
1127
|
});
|
|
@@ -1089,7 +1134,7 @@ function buildNegativeConfigTestCases(context) {
|
|
|
1089
1134
|
name: 'Missing OpenAPI file path',
|
|
1090
1135
|
run: async(options) => {
|
|
1091
1136
|
const configPath = await writeWizardConfig('wizard-invalid-openapi-file', {
|
|
1092
|
-
appName: '
|
|
1137
|
+
appName: 'wizard-e2e-negative-openapi',
|
|
1093
1138
|
mode: 'create-system',
|
|
1094
1139
|
source: { type: 'openapi-file', filePath: '/tmp/does-not-exist.json' }
|
|
1095
1140
|
});
|
|
@@ -1102,7 +1147,7 @@ function buildNegativeConfigTestCases(context) {
|
|
|
1102
1147
|
name: 'OpenAPI URL missing url',
|
|
1103
1148
|
run: async(options) => {
|
|
1104
1149
|
const configPath = await writeWizardConfig('wizard-invalid-openapi-url', {
|
|
1105
|
-
appName: '
|
|
1150
|
+
appName: 'wizard-e2e-negative-openapi-url',
|
|
1106
1151
|
mode: 'create-system',
|
|
1107
1152
|
source: { type: 'openapi-url' }
|
|
1108
1153
|
});
|
|
@@ -1126,7 +1171,7 @@ function buildNegativeCredentialTestCases(context) {
|
|
|
1126
1171
|
name: 'Add datasource missing systemIdOrKey',
|
|
1127
1172
|
run: async(options) => {
|
|
1128
1173
|
const configPath = await writeWizardConfig('wizard-invalid-add-datasource', {
|
|
1129
|
-
appName: '
|
|
1174
|
+
appName: 'wizard-e2e-negative-add-datasource',
|
|
1130
1175
|
mode: 'add-datasource',
|
|
1131
1176
|
source: { type: 'known-platform', platform: 'hubspot' }
|
|
1132
1177
|
});
|
|
@@ -1139,7 +1184,7 @@ function buildNegativeCredentialTestCases(context) {
|
|
|
1139
1184
|
name: 'Credential select missing credentialIdOrKey',
|
|
1140
1185
|
run: async(options) => {
|
|
1141
1186
|
const configPath = await writeWizardConfig('wizard-invalid-credential-select', {
|
|
1142
|
-
appName: '
|
|
1187
|
+
appName: 'wizard-e2e-negative-credential-select',
|
|
1143
1188
|
mode: 'create-system',
|
|
1144
1189
|
source: { type: 'known-platform', platform: 'hubspot' },
|
|
1145
1190
|
credential: { action: 'select' }
|
|
@@ -1153,7 +1198,7 @@ function buildNegativeCredentialTestCases(context) {
|
|
|
1153
1198
|
name: 'Credential create missing config',
|
|
1154
1199
|
run: async(options) => {
|
|
1155
1200
|
const configPath = await writeWizardConfig('wizard-invalid-credential-create', {
|
|
1156
|
-
appName: '
|
|
1201
|
+
appName: 'wizard-e2e-negative-credential-create',
|
|
1157
1202
|
mode: 'create-system',
|
|
1158
1203
|
source: { type: 'known-platform', platform: 'hubspot' },
|
|
1159
1204
|
credential: { action: 'create' }
|
|
@@ -1176,13 +1221,7 @@ function buildNegativeCredentialTestCases(context) {
|
|
|
1176
1221
|
* @throws {Error} If validation succeeds or expected message not found
|
|
1177
1222
|
*/
|
|
1178
1223
|
async function runValidationExpectFailure(appName, context, options, expectedMessage = null) {
|
|
1179
|
-
const validateArgs = [
|
|
1180
|
-
'bin/aifabrix.js',
|
|
1181
|
-
'validate',
|
|
1182
|
-
appName,
|
|
1183
|
-
'--type',
|
|
1184
|
-
'external'
|
|
1185
|
-
];
|
|
1224
|
+
const validateArgs = ['bin/aifabrix.js', 'validate', appName];
|
|
1186
1225
|
const result = await runCommand('node', validateArgs, options);
|
|
1187
1226
|
if (result.success) {
|
|
1188
1227
|
throw new Error('Expected validation to fail, but it succeeded.');
|
|
@@ -1363,7 +1402,7 @@ function buildNegativeRbacTestCases(context) {
|
|
|
1363
1402
|
type: 'negative',
|
|
1364
1403
|
name: 'RBAC missing role referenced in permissions',
|
|
1365
1404
|
run: async(options) => {
|
|
1366
|
-
const appName = '
|
|
1405
|
+
const appName = 'wizard-e2e-negative-rbac-missing-role';
|
|
1367
1406
|
const appPath = await createSystemForNegativeTest(appName, 'wizard-valid-for-rbac-test', context, options);
|
|
1368
1407
|
await corruptSystemFileWithInvalidRole(appPath);
|
|
1369
1408
|
await runValidationExpectFailure(appName, context, options, 'references role "non-existent-role" which does not exist');
|
|
@@ -1375,7 +1414,11 @@ function buildNegativeRbacTestCases(context) {
|
|
|
1375
1414
|
type: 'negative',
|
|
1376
1415
|
name: 'RBAC invalid YAML syntax',
|
|
1377
1416
|
run: async(options) => {
|
|
1378
|
-
const appName = '
|
|
1417
|
+
const appName = 'wizard-e2e-negative-rbac-invalid-yaml';
|
|
1418
|
+
const appDir = path.join(process.cwd(), 'integration', appName);
|
|
1419
|
+
if (fsSync.existsSync(appDir)) {
|
|
1420
|
+
await fs.rm(appDir, { recursive: true, force: true });
|
|
1421
|
+
}
|
|
1379
1422
|
const appPath = await createSystemForNegativeTest(appName, 'wizard-valid-for-rbac-yaml-test', context, options);
|
|
1380
1423
|
await corruptRbacFile(appPath);
|
|
1381
1424
|
await runValidationExpectFailure(appName, context, options, 'Invalid YAML syntax in rbac.yaml');
|
|
@@ -1409,7 +1452,7 @@ function buildNegativeDimensionTestCases(context) {
|
|
|
1409
1452
|
createTestCase(
|
|
1410
1453
|
'2.14',
|
|
1411
1454
|
'Datasource missing dimensions in fieldMappings',
|
|
1412
|
-
'
|
|
1455
|
+
'wizard-e2e-negative-dimension-missing',
|
|
1413
1456
|
'wizard-valid-for-dimension-test',
|
|
1414
1457
|
corruptDatasourceRemoveDimensions,
|
|
1415
1458
|
'Missing required property "dimensions"'
|
|
@@ -1417,7 +1460,7 @@ function buildNegativeDimensionTestCases(context) {
|
|
|
1417
1460
|
createTestCase(
|
|
1418
1461
|
'2.15',
|
|
1419
1462
|
'Datasource invalid dimension key pattern',
|
|
1420
|
-
'
|
|
1463
|
+
'wizard-e2e-negative-dimension-invalid-key',
|
|
1421
1464
|
'wizard-valid-for-dimension-key-test',
|
|
1422
1465
|
corruptDatasourceInvalidDimensionKey,
|
|
1423
1466
|
'Must be at most 40 characters'
|
|
@@ -1425,7 +1468,7 @@ function buildNegativeDimensionTestCases(context) {
|
|
|
1425
1468
|
createTestCase(
|
|
1426
1469
|
'2.16',
|
|
1427
1470
|
'Datasource invalid attribute path pattern',
|
|
1428
|
-
'
|
|
1471
|
+
'wizard-e2e-negative-dimension-invalid-path',
|
|
1429
1472
|
'wizard-valid-for-dimension-path-test',
|
|
1430
1473
|
corruptDatasourceInvalidAttributePath,
|
|
1431
1474
|
'must match pattern'
|
|
@@ -1433,7 +1476,7 @@ function buildNegativeDimensionTestCases(context) {
|
|
|
1433
1476
|
createTestCase(
|
|
1434
1477
|
'2.17',
|
|
1435
1478
|
'Datasource dimensions as array instead of object',
|
|
1436
|
-
'
|
|
1479
|
+
'wizard-e2e-negative-dimension-array',
|
|
1437
1480
|
'wizard-valid-for-dimension-array-test',
|
|
1438
1481
|
corruptDatasourceDimensionsAsArray,
|
|
1439
1482
|
'Expected object, got undefined'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
appName:
|
|
1
|
+
appName: wizard-e2e-e2e
|
|
2
2
|
mode: create-system
|
|
3
3
|
source:
|
|
4
4
|
type: openapi-file
|
|
5
|
-
filePath: /workspace/aifabrix-builder/integration/hubspot/companies.json
|
|
5
|
+
filePath: /workspace/aifabrix-builder/integration/hubspot-test/companies.json
|
|
6
6
|
credential:
|
|
7
7
|
action: skip
|
|
8
8
|
preferences:
|
|
@@ -17,7 +17,7 @@ const { ApiClient } = require('./index');
|
|
|
17
17
|
* @async
|
|
18
18
|
* @function testDatasourceE2E
|
|
19
19
|
* @param {string} dataplaneUrl - Dataplane base URL
|
|
20
|
-
* @param {string} sourceIdOrKey - Source ID or datasource key (e.g. hubspot-test-
|
|
20
|
+
* @param {string} sourceIdOrKey - Source ID or datasource key (e.g. hubspot-test-contacts)
|
|
21
21
|
* @param {Object} authConfig - Authentication configuration (must have token or apiKey; client creds rejected)
|
|
22
22
|
* @param {Object} [body] - Optional request body (e.g. includeDebug, testCrud, recordId, cleanup, primaryKeyValue)
|
|
23
23
|
* @param {Object} [options] - Optional options
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Service users API functions (create
|
|
2
|
+
* @fileoverview Service users API functions (create, list, rotate-secret, delete, update)
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
@@ -36,6 +36,115 @@ async function createServiceUser(controllerUrl, authConfig, body) {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* List service users with optional pagination and search
|
|
41
|
+
* GET /api/v1/service-users
|
|
42
|
+
* @requiresPermission {Controller} service-user:read
|
|
43
|
+
* @async
|
|
44
|
+
* @function listServiceUsers
|
|
45
|
+
* @param {string} controllerUrl - Controller base URL
|
|
46
|
+
* @param {Object} authConfig - Authentication configuration (bearer or client-credentials)
|
|
47
|
+
* @param {Object} [options] - Query options
|
|
48
|
+
* @param {number} [options.page] - Page number
|
|
49
|
+
* @param {number} [options.pageSize] - Items per page
|
|
50
|
+
* @param {string} [options.sort] - Sort field/direction
|
|
51
|
+
* @param {string} [options.filter] - Filter expression
|
|
52
|
+
* @param {string} [options.search] - Search term
|
|
53
|
+
* @returns {Promise<Object>} Response with data (array), meta, links
|
|
54
|
+
* @throws {Error} If request fails (401/403 or network)
|
|
55
|
+
*/
|
|
56
|
+
async function listServiceUsers(controllerUrl, authConfig, options = {}) {
|
|
57
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
58
|
+
const params = {};
|
|
59
|
+
if (options.page !== undefined && options.page !== null) params.page = options.page;
|
|
60
|
+
if (options.pageSize !== undefined && options.pageSize !== null) params.pageSize = options.pageSize;
|
|
61
|
+
if (options.sort) params.sort = options.sort;
|
|
62
|
+
if (options.filter) params.filter = options.filter;
|
|
63
|
+
if (options.search) params.search = options.search;
|
|
64
|
+
return await client.get('/api/v1/service-users', { params });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Regenerate (rotate) secret for a service user. New secret is returned once only.
|
|
69
|
+
* POST /api/v1/service-users/{id}/regenerate-secret
|
|
70
|
+
* @requiresPermission {Controller} service-user:update
|
|
71
|
+
* @async
|
|
72
|
+
* @function regenerateSecretServiceUser
|
|
73
|
+
* @param {string} controllerUrl - Controller base URL
|
|
74
|
+
* @param {Object} authConfig - Authentication configuration
|
|
75
|
+
* @param {string} id - Service user ID (UUID)
|
|
76
|
+
* @returns {Promise<Object>} Response with data.clientSecret
|
|
77
|
+
* @throws {Error} If request fails (401/403/404 or network)
|
|
78
|
+
*/
|
|
79
|
+
async function regenerateSecretServiceUser(controllerUrl, authConfig, id) {
|
|
80
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
81
|
+
return await client.post(`/api/v1/service-users/${encodeURIComponent(id)}/regenerate-secret`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Delete (deactivate) a service user
|
|
86
|
+
* DELETE /api/v1/service-users/{id}
|
|
87
|
+
* @requiresPermission {Controller} service-user:delete
|
|
88
|
+
* @async
|
|
89
|
+
* @function deleteServiceUser
|
|
90
|
+
* @param {string} controllerUrl - Controller base URL
|
|
91
|
+
* @param {Object} authConfig - Authentication configuration
|
|
92
|
+
* @param {string} id - Service user ID (UUID)
|
|
93
|
+
* @returns {Promise<Object>} Response (data may be null)
|
|
94
|
+
* @throws {Error} If request fails (401/403/404 or network)
|
|
95
|
+
*/
|
|
96
|
+
async function deleteServiceUser(controllerUrl, authConfig, id) {
|
|
97
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
98
|
+
return await client.delete(`/api/v1/service-users/${encodeURIComponent(id)}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Update group assignments for a service user
|
|
103
|
+
* PUT /api/v1/service-users/{id}/groups
|
|
104
|
+
* @requiresPermission {Controller} service-user:update
|
|
105
|
+
* @async
|
|
106
|
+
* @function updateGroupsServiceUser
|
|
107
|
+
* @param {string} controllerUrl - Controller base URL
|
|
108
|
+
* @param {Object} authConfig - Authentication configuration
|
|
109
|
+
* @param {string} id - Service user ID (UUID)
|
|
110
|
+
* @param {Object} body - Request body
|
|
111
|
+
* @param {string[]} body.groupNames - Group names to set
|
|
112
|
+
* @returns {Promise<Object>} Response with data.id, data.groupNames
|
|
113
|
+
* @throws {Error} If request fails (400/401/403/404 or network)
|
|
114
|
+
*/
|
|
115
|
+
async function updateGroupsServiceUser(controllerUrl, authConfig, id, body) {
|
|
116
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
117
|
+
return await client.put(`/api/v1/service-users/${encodeURIComponent(id)}/groups`, {
|
|
118
|
+
body: { groupNames: body.groupNames }
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Update redirect URIs for a service user (min 1). Controller merges in its callback URL.
|
|
124
|
+
* PUT /api/v1/service-users/{id}/redirect-uris
|
|
125
|
+
* @requiresPermission {Controller} service-user:update
|
|
126
|
+
* @async
|
|
127
|
+
* @function updateRedirectUrisServiceUser
|
|
128
|
+
* @param {string} controllerUrl - Controller base URL
|
|
129
|
+
* @param {Object} authConfig - Authentication configuration
|
|
130
|
+
* @param {string} id - Service user ID (UUID)
|
|
131
|
+
* @param {Object} body - Request body
|
|
132
|
+
* @param {string[]} body.redirectUris - Redirect URIs (min 1)
|
|
133
|
+
* @returns {Promise<Object>} Response with data.id, data.redirectUris
|
|
134
|
+
* @throws {Error} If request fails (400/401/403/404 or network)
|
|
135
|
+
*/
|
|
136
|
+
async function updateRedirectUrisServiceUser(controllerUrl, authConfig, id, body) {
|
|
137
|
+
const client = new ApiClient(controllerUrl, authConfig);
|
|
138
|
+
return await client.put(`/api/v1/service-users/${encodeURIComponent(id)}/redirect-uris`, {
|
|
139
|
+
body: { redirectUris: body.redirectUris }
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
39
143
|
module.exports = {
|
|
40
|
-
createServiceUser
|
|
144
|
+
createServiceUser,
|
|
145
|
+
listServiceUsers,
|
|
146
|
+
regenerateSecretServiceUser,
|
|
147
|
+
deleteServiceUser,
|
|
148
|
+
updateGroupsServiceUser,
|
|
149
|
+
updateRedirectUrisServiceUser
|
|
41
150
|
};
|
|
@@ -22,3 +22,44 @@
|
|
|
22
22
|
* @property {boolean} [success] - Optional wrapper flag
|
|
23
23
|
* @property {string} [createdAt] - Optional creation timestamp (ISO 8601)
|
|
24
24
|
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Single service user item in list response
|
|
28
|
+
* @typedef {Object} ListServiceUserItem
|
|
29
|
+
* @property {string} id - Service user ID (UUID)
|
|
30
|
+
* @property {string} [username] - Username
|
|
31
|
+
* @property {string} [email] - Email
|
|
32
|
+
* @property {string} [clientId] - OAuth2 client ID
|
|
33
|
+
* @property {boolean} [active] - Whether the service user is active
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List service users response (Controller GET /api/v1/service-users)
|
|
38
|
+
* @typedef {Object} ListServiceUsersResponse
|
|
39
|
+
* @property {ListServiceUserItem[]} data - Array of service users
|
|
40
|
+
* @property {Object} [meta] - Pagination metadata (e.g. total, page, pageSize)
|
|
41
|
+
* @property {Object} [links] - Pagination links
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Regenerate secret response (Controller POST .../regenerate-secret). clientSecret is one-time-only.
|
|
46
|
+
* @typedef {Object} RegenerateSecretServiceUserResponse
|
|
47
|
+
* @property {Object} data - Response data
|
|
48
|
+
* @property {string} data.clientSecret - New client secret (shown once only)
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Update groups response (Controller PUT .../groups)
|
|
53
|
+
* @typedef {Object} UpdateGroupsServiceUserResponse
|
|
54
|
+
* @property {Object} data - Response data
|
|
55
|
+
* @property {string} data.id - Service user ID
|
|
56
|
+
* @property {string[]} data.groupNames - Updated group names
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Update redirect URIs response (Controller PUT .../redirect-uris)
|
|
61
|
+
* @typedef {Object} UpdateRedirectUrisServiceUserResponse
|
|
62
|
+
* @property {Object} data - Response data
|
|
63
|
+
* @property {string} data.id - Service user ID
|
|
64
|
+
* @property {string[]} data.redirectUris - Updated redirect URIs
|
|
65
|
+
*/
|
package/lib/app/register.js
CHANGED
|
@@ -150,7 +150,9 @@ async function registerApplication(appKey, options = {}) {
|
|
|
150
150
|
logger.log(chalk.blue('📋 Registering application...\n'));
|
|
151
151
|
|
|
152
152
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
153
|
-
const
|
|
153
|
+
const config = require('../core/config');
|
|
154
|
+
await config.ensureSecretsEncryptionKey();
|
|
155
|
+
const { resolveEnvironment } = config;
|
|
154
156
|
|
|
155
157
|
// Load application config
|
|
156
158
|
const { variables, created } = await loadVariablesYaml(appKey);
|
package/lib/app/rotate-secret.js
CHANGED
|
@@ -339,6 +339,9 @@ async function executeRotation(appKey, actualControllerUrl, environment, token)
|
|
|
339
339
|
async function rotateSecret(appKey, _options = {}) {
|
|
340
340
|
logger.log(chalk.yellow('⚠️ This will invalidate the old ClientSecret!\n'));
|
|
341
341
|
|
|
342
|
+
const { ensureSecretsEncryptionKey } = require('../core/config');
|
|
343
|
+
await ensureSecretsEncryptionKey();
|
|
344
|
+
|
|
342
345
|
const { controllerUrl, environment } = await resolveControllerAndEnvironment();
|
|
343
346
|
const config = await getConfig();
|
|
344
347
|
const { token, actualControllerUrl } = await getRotationAuthToken(controllerUrl, config);
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -140,12 +140,12 @@ Examples:
|
|
|
140
140
|
$ aifabrix wizard my-integration --silent Run headless with integration/my-integration/wizard.yaml (no prompts)
|
|
141
141
|
$ aifabrix wizard -a my-integration Same as above (app name set)
|
|
142
142
|
$ aifabrix wizard --config wizard.yaml Run headless from a wizard config file
|
|
143
|
-
$ aifabrix wizard hubspot-test
|
|
143
|
+
$ aifabrix wizard hubspot-test --debug Enable debug output and save debug manifests on validation failure
|
|
144
144
|
|
|
145
145
|
Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
|
|
146
146
|
To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
|
|
147
147
|
Headless config must include: appName, mode (create-system|add-datasource), source (type + filePath/url/platform).
|
|
148
|
-
See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`;
|
|
148
|
+
See integration/hubspot-test/wizard-hubspot-e2e.yaml for an example.`;
|
|
149
149
|
program.command('wizard [appName]')
|
|
150
150
|
.description('Create or extend external systems (OpenAPI, MCP, or known platforms like HubSpot) via guided steps or a config file')
|
|
151
151
|
.option('-a, --app <app>', 'Application name (synonym for positional appName)')
|