@jupiterone/integration-sdk-cli 8.34.0 → 9.0.0-beta.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.
- package/dist/src/commands/document.js +1 -2
- package/dist/src/commands/document.js.map +1 -1
- package/dist/src/commands/generate-ingestion-sources-config.d.ts +18 -0
- package/dist/src/commands/generate-ingestion-sources-config.js +96 -0
- package/dist/src/commands/generate-ingestion-sources-config.js.map +1 -0
- package/dist/src/commands/generate-integration-graph-schema.js +1 -40
- package/dist/src/commands/generate-integration-graph-schema.js.map +1 -1
- package/dist/src/commands/index.d.ts +1 -0
- package/dist/src/commands/index.js +1 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/visualize-types.js +8 -11
- package/dist/src/commands/visualize-types.js.map +1 -1
- package/dist/src/config.d.ts +7 -0
- package/dist/src/config.js +37 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/neo4j/neo4jUtilities.js +1 -2
- package/dist/src/neo4j/neo4jUtilities.js.map +1 -1
- package/dist/src/utils/getSortedJupiterOneTypes.js +1 -2
- package/dist/src/utils/getSortedJupiterOneTypes.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/commands/generate-ingestion-sources-config.test.ts +185 -0
- package/src/commands/generate-ingestion-sources-config.ts +112 -0
- package/src/commands/generate-integration-graph-schema.ts +1 -44
- package/src/commands/index.ts +1 -0
- package/src/config.ts +42 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntegrationIngestionConfigFieldMap,
|
|
3
|
+
IntegrationStep,
|
|
4
|
+
IntegrationInstanceConfig,
|
|
5
|
+
} from '@jupiterone/integration-sdk-core';
|
|
6
|
+
import { generateIngestionSourcesConfig } from './generate-ingestion-sources-config';
|
|
7
|
+
|
|
8
|
+
describe('#generateIngestionSourcesConfig', () => {
|
|
9
|
+
const INGESTION_SOURCE_IDS = {
|
|
10
|
+
FINDING_ALERTS: 'finding-alerts',
|
|
11
|
+
FETCH_REPOS: 'fetch-repos',
|
|
12
|
+
TEST_SOURCE: 'test-source',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ingestionConfig: IntegrationIngestionConfigFieldMap = {
|
|
16
|
+
[INGESTION_SOURCE_IDS.FINDING_ALERTS]: {
|
|
17
|
+
title: 'Finding Alerts',
|
|
18
|
+
description:
|
|
19
|
+
'Dependabot vulnerability alert ingestion and Code scanning alerts',
|
|
20
|
+
defaultsToDisabled: true,
|
|
21
|
+
},
|
|
22
|
+
[INGESTION_SOURCE_IDS.FETCH_REPOS]: {
|
|
23
|
+
title: 'Fetch repos',
|
|
24
|
+
description: 'This is an ingestion source created for test purposes',
|
|
25
|
+
defaultsToDisabled: false,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
it('should return the ingestionConfig with empty childIngestionSources', () => {
|
|
30
|
+
const integrationSteps: IntegrationStep<IntegrationInstanceConfig>[] = [
|
|
31
|
+
{
|
|
32
|
+
id: 'fetch-vulnerability-alerts',
|
|
33
|
+
name: 'Fetch Vulnerability Alerts',
|
|
34
|
+
entities: [
|
|
35
|
+
{
|
|
36
|
+
resourceName: 'GitHub Vulnerability Alerts',
|
|
37
|
+
_type: 'github_finding',
|
|
38
|
+
_class: ['Finding'],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
relationships: [],
|
|
42
|
+
dependsOn: ['fetch-repos'],
|
|
43
|
+
ingestionSourceId: INGESTION_SOURCE_IDS.FINDING_ALERTS,
|
|
44
|
+
executionHandler: jest.fn(),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
const ingestionSourcesConfig = generateIngestionSourcesConfig(
|
|
48
|
+
ingestionConfig,
|
|
49
|
+
integrationSteps,
|
|
50
|
+
);
|
|
51
|
+
expect(
|
|
52
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.FINDING_ALERTS],
|
|
53
|
+
).toMatchObject(ingestionConfig[INGESTION_SOURCE_IDS.FINDING_ALERTS]);
|
|
54
|
+
// childIngestionSources is empty because there are no steps that depends on fetch-vulnerability-alerts
|
|
55
|
+
expect(
|
|
56
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.FINDING_ALERTS]
|
|
57
|
+
.childIngestionSources,
|
|
58
|
+
).toBeEmpty();
|
|
59
|
+
// ingestionSourcesConfig[INGESTION_SOURCE_IDS.FETCH_REPOS] is undefined because there are no steps using that ingestionSourceId
|
|
60
|
+
expect(
|
|
61
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.FETCH_REPOS],
|
|
62
|
+
).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return the ingestionConfig with childIngestionSources', () => {
|
|
66
|
+
const integrationSteps: IntegrationStep<IntegrationInstanceConfig>[] = [
|
|
67
|
+
{
|
|
68
|
+
id: 'fetch-repos',
|
|
69
|
+
name: 'Fetch Repos',
|
|
70
|
+
entities: [
|
|
71
|
+
{
|
|
72
|
+
resourceName: 'Github Repo',
|
|
73
|
+
_type: 'github_repo',
|
|
74
|
+
_class: ['CodeRepo'],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
relationships: [],
|
|
78
|
+
dependsOn: ['fetch-account'],
|
|
79
|
+
ingestionSourceId: INGESTION_SOURCE_IDS.FETCH_REPOS,
|
|
80
|
+
executionHandler: jest.fn(),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'fetch-vulnerability-alerts',
|
|
84
|
+
name: 'Fetch Vulnerability Alerts',
|
|
85
|
+
entities: [
|
|
86
|
+
{
|
|
87
|
+
resourceName: 'GitHub Vulnerability Alerts',
|
|
88
|
+
_type: 'github_finding',
|
|
89
|
+
_class: ['Finding'],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
relationships: [],
|
|
93
|
+
dependsOn: ['fetch-repos'],
|
|
94
|
+
ingestionSourceId: INGESTION_SOURCE_IDS.FINDING_ALERTS,
|
|
95
|
+
executionHandler: jest.fn(),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'fetch-issues',
|
|
99
|
+
name: 'Fetch Issues',
|
|
100
|
+
entities: [
|
|
101
|
+
{
|
|
102
|
+
resourceName: 'GitHub Issue',
|
|
103
|
+
_type: 'github_issue',
|
|
104
|
+
_class: ['Issue'],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
relationships: [],
|
|
108
|
+
dependsOn: ['fetch-repos', 'fetch-users', 'fetch-collaborators'],
|
|
109
|
+
executionHandler: jest.fn(),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'fetch-teams',
|
|
113
|
+
name: 'Fetch Teams',
|
|
114
|
+
entities: [
|
|
115
|
+
{
|
|
116
|
+
resourceName: 'GitHub Team',
|
|
117
|
+
_type: 'github_team',
|
|
118
|
+
_class: ['UserGroup'],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
relationships: [],
|
|
122
|
+
dependsOn: ['fetch-account'],
|
|
123
|
+
executionHandler: jest.fn(),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
const ingestionSourcesConfig = generateIngestionSourcesConfig(
|
|
127
|
+
ingestionConfig,
|
|
128
|
+
integrationSteps,
|
|
129
|
+
);
|
|
130
|
+
// Original object doesn't change
|
|
131
|
+
expect(
|
|
132
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.FETCH_REPOS],
|
|
133
|
+
).toMatchObject(ingestionConfig[INGESTION_SOURCE_IDS.FETCH_REPOS]);
|
|
134
|
+
// New property added
|
|
135
|
+
expect(
|
|
136
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.FETCH_REPOS]
|
|
137
|
+
.childIngestionSources,
|
|
138
|
+
).toEqual(['fetch-vulnerability-alerts', 'fetch-issues']);
|
|
139
|
+
// For FINDING_ALERTS the ingestionConfig keep exactly the same
|
|
140
|
+
expect(
|
|
141
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.FINDING_ALERTS],
|
|
142
|
+
).toMatchObject(ingestionConfig[INGESTION_SOURCE_IDS.FINDING_ALERTS]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should not add the source if it does not exist in the ingestionConfig', () => {
|
|
146
|
+
const integrationSteps: IntegrationStep<IntegrationInstanceConfig>[] = [
|
|
147
|
+
{
|
|
148
|
+
id: 'fetch-repos',
|
|
149
|
+
name: 'Fetch Repos',
|
|
150
|
+
entities: [
|
|
151
|
+
{
|
|
152
|
+
resourceName: 'Github Repo',
|
|
153
|
+
_type: 'github_repo',
|
|
154
|
+
_class: ['CodeRepo'],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
relationships: [],
|
|
158
|
+
dependsOn: ['fetch-account'],
|
|
159
|
+
ingestionSourceId: INGESTION_SOURCE_IDS.TEST_SOURCE,
|
|
160
|
+
executionHandler: jest.fn(),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'fetch-issues',
|
|
164
|
+
name: 'Fetch Issues',
|
|
165
|
+
entities: [
|
|
166
|
+
{
|
|
167
|
+
resourceName: 'GitHub Issue',
|
|
168
|
+
_type: 'github_issue',
|
|
169
|
+
_class: ['Issue'],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
relationships: [],
|
|
173
|
+
dependsOn: ['fetch-repos', 'fetch-users', 'fetch-collaborators'],
|
|
174
|
+
executionHandler: jest.fn(),
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
const ingestionSourcesConfig = generateIngestionSourcesConfig(
|
|
178
|
+
ingestionConfig,
|
|
179
|
+
integrationSteps,
|
|
180
|
+
);
|
|
181
|
+
expect(
|
|
182
|
+
ingestionSourcesConfig[INGESTION_SOURCE_IDS.TEST_SOURCE],
|
|
183
|
+
).toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntegrationIngestionConfigField,
|
|
3
|
+
IntegrationIngestionConfigFieldMap,
|
|
4
|
+
IntegrationSourceId,
|
|
5
|
+
Step,
|
|
6
|
+
StepExecutionContext,
|
|
7
|
+
} from '@jupiterone/integration-sdk-core';
|
|
8
|
+
import { createCommand } from 'commander';
|
|
9
|
+
import { loadConfigFromTarget } from '../config';
|
|
10
|
+
import { promises as fs } from 'fs';
|
|
11
|
+
import * as log from '../log';
|
|
12
|
+
|
|
13
|
+
/* eslint-disable no-console */
|
|
14
|
+
export function generateIngestionSourcesConfigCommand() {
|
|
15
|
+
return createCommand('generate-ingestion-sources-config')
|
|
16
|
+
.description(
|
|
17
|
+
'generate ingestion sources config from ingestion config and steps data',
|
|
18
|
+
)
|
|
19
|
+
.option(
|
|
20
|
+
'-o, --output-file <path>',
|
|
21
|
+
'project relative path to generated ingestion sources config file',
|
|
22
|
+
)
|
|
23
|
+
.option(
|
|
24
|
+
'-p, --project-path <directory>',
|
|
25
|
+
'path to integration project directory',
|
|
26
|
+
process.cwd(),
|
|
27
|
+
)
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
const { projectPath, outputFile } = options;
|
|
30
|
+
|
|
31
|
+
log.info(
|
|
32
|
+
`Generating ingestion sources config (projectPath=${projectPath}, outputFile=${outputFile})`,
|
|
33
|
+
);
|
|
34
|
+
const config = await loadConfigFromTarget(projectPath);
|
|
35
|
+
if (!config.ingestionConfig) {
|
|
36
|
+
log.info(
|
|
37
|
+
'Skipping the generation of ingestion sources config file as there is no ingestionConfig present.',
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
const ingestionSourcesConfig = generateIngestionSourcesConfig(
|
|
41
|
+
config.ingestionConfig,
|
|
42
|
+
config.integrationSteps,
|
|
43
|
+
);
|
|
44
|
+
if (outputFile) {
|
|
45
|
+
await fs.writeFile(
|
|
46
|
+
outputFile,
|
|
47
|
+
JSON.stringify(ingestionSourcesConfig),
|
|
48
|
+
{
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
console.log(JSON.stringify(ingestionSourcesConfig, null, 2));
|
|
54
|
+
}
|
|
55
|
+
log.info('Successfully generated ingestion sources config file');
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type EnhancedIntegrationIngestionConfigFieldMap = Record<
|
|
61
|
+
IntegrationSourceId,
|
|
62
|
+
IntegrationIngestionConfigField & { childIngestionSources?: string[] }
|
|
63
|
+
>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generates an ingestionConfig with childIngestionSources taking into account
|
|
67
|
+
* the integration steps that come as argument.
|
|
68
|
+
* The childIngestionSources will be the list of stepIds that have any dependencies
|
|
69
|
+
* on the steps that match the ingestion sources specified.
|
|
70
|
+
*
|
|
71
|
+
* @export
|
|
72
|
+
* @template TStepExecutionContext
|
|
73
|
+
* @param {IntegrationIngestionConfigData} ingestionConfigData ingestionData without childIngestionSources
|
|
74
|
+
* @param {Step<TStepExecutionContext>[]} integrationSteps total list of integration steps
|
|
75
|
+
* @return {*} {IntegrationIngestionConfigFieldMap} ingestionData with childIngestionSources
|
|
76
|
+
*/
|
|
77
|
+
export function generateIngestionSourcesConfig<
|
|
78
|
+
TStepExecutionContext extends StepExecutionContext,
|
|
79
|
+
>(
|
|
80
|
+
ingestionConfig: IntegrationIngestionConfigFieldMap,
|
|
81
|
+
integrationSteps: Step<TStepExecutionContext>[],
|
|
82
|
+
): EnhancedIntegrationIngestionConfigFieldMap {
|
|
83
|
+
const newIngestionConfig: EnhancedIntegrationIngestionConfigFieldMap = {};
|
|
84
|
+
Object.keys(ingestionConfig).forEach((key) => {
|
|
85
|
+
if (ingestionConfig[key]) {
|
|
86
|
+
// Get the stepIds that match the current ingestionSourceId
|
|
87
|
+
const matchedIntegrationStepIds = integrationSteps
|
|
88
|
+
.filter((step) => step.ingestionSourceId === key)
|
|
89
|
+
.map(({ id }) => id);
|
|
90
|
+
if (!matchedIntegrationStepIds.length) {
|
|
91
|
+
// Skip iteration if there are no steps pointing to the current ingestionSourceId
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Get the stepIds that have any dependencies on the matched step ids
|
|
95
|
+
const childIngestionSources = integrationSteps
|
|
96
|
+
.filter((step) =>
|
|
97
|
+
step.dependsOn?.some((value) =>
|
|
98
|
+
matchedIntegrationStepIds.includes(value),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
.map(({ id }) => id);
|
|
102
|
+
// Generate ingestionConfig with the childIngestionSources
|
|
103
|
+
newIngestionConfig[key] = {
|
|
104
|
+
...ingestionConfig[key],
|
|
105
|
+
childIngestionSources,
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
log.warn(`The key ${key} does not exist in the ingestionConfig`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return newIngestionConfig;
|
|
112
|
+
}
|
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
StepRelationshipMetadata,
|
|
8
8
|
} from '@jupiterone/integration-sdk-core';
|
|
9
9
|
import { createCommand } from 'commander';
|
|
10
|
-
import
|
|
11
|
-
import { IntegrationInvocationConfigLoadError, loadConfig } from '../config';
|
|
10
|
+
import { loadConfigFromTarget } from '../config';
|
|
12
11
|
import { promises as fs } from 'fs';
|
|
13
12
|
import * as log from '../log';
|
|
14
13
|
|
|
@@ -51,48 +50,6 @@ export function generateIntegrationGraphSchemaCommand() {
|
|
|
51
50
|
});
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
function loadConfigFromSrc(projectPath: string) {
|
|
55
|
-
return loadConfig(path.join(projectPath, 'src'));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function loadConfigFromDist(projectPath: string) {
|
|
59
|
-
return loadConfig(path.join(projectPath, 'dist'));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The way that integration npm packages are distributed has changed over time.
|
|
64
|
-
* This function handles different cases where the invocation config has
|
|
65
|
-
* traditionally lived to support backwards compatibility and make adoption
|
|
66
|
-
* easier.
|
|
67
|
-
*/
|
|
68
|
-
async function loadConfigFromTarget(projectPath: string) {
|
|
69
|
-
let configFromSrcErr: Error | undefined;
|
|
70
|
-
let configFromDistErr: Error | undefined;
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const configFromSrc = await loadConfigFromSrc(projectPath);
|
|
74
|
-
return configFromSrc;
|
|
75
|
-
} catch (err) {
|
|
76
|
-
configFromSrcErr = err;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const configFromDist = await loadConfigFromDist(projectPath);
|
|
81
|
-
return configFromDist;
|
|
82
|
-
} catch (err) {
|
|
83
|
-
configFromDistErr = err;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const combinedError = configFromDistErr
|
|
87
|
-
? configFromSrcErr + ', ' + configFromDistErr
|
|
88
|
-
: configFromSrcErr;
|
|
89
|
-
|
|
90
|
-
throw new IntegrationInvocationConfigLoadError(
|
|
91
|
-
'Error loading integration invocation configuration. Ensure "invocationConfig" is exported from src/index or dist/index. Additional details: ' +
|
|
92
|
-
combinedError,
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
53
|
type IntegrationGraphSchemaEntityMetadata = {
|
|
97
54
|
resourceName: string;
|
|
98
55
|
_class: string | string[];
|
package/src/commands/index.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -51,6 +51,48 @@ export function loadInvocationConfig(
|
|
|
51
51
|
return integrationModule.invocationConfig as IntegrationInvocationConfig;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function loadConfigFromSrc(projectPath: string) {
|
|
55
|
+
return loadConfig(path.join(projectPath, 'src'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadConfigFromDist(projectPath: string) {
|
|
59
|
+
return loadConfig(path.join(projectPath, 'dist'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The way that integration npm packages are distributed has changed over time.
|
|
64
|
+
* This function handles different cases where the invocation config has
|
|
65
|
+
* traditionally lived to support backwards compatibility and make adoption
|
|
66
|
+
* easier.
|
|
67
|
+
*/
|
|
68
|
+
export async function loadConfigFromTarget(projectPath: string) {
|
|
69
|
+
let configFromSrcErr: Error | undefined;
|
|
70
|
+
let configFromDistErr: Error | undefined;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const configFromSrc = await loadConfigFromSrc(projectPath);
|
|
74
|
+
return configFromSrc;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
configFromSrcErr = err;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const configFromDist = await loadConfigFromDist(projectPath);
|
|
81
|
+
return configFromDist;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
configFromDistErr = err;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const combinedError = configFromDistErr
|
|
87
|
+
? configFromSrcErr + ', ' + configFromDistErr
|
|
88
|
+
: configFromSrcErr;
|
|
89
|
+
|
|
90
|
+
throw new IntegrationInvocationConfigLoadError(
|
|
91
|
+
'Error loading integration invocation configuration. Ensure "invocationConfig" is exported from src/index or dist/index. Additional details: ' +
|
|
92
|
+
combinedError,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
54
96
|
async function isTypescriptPresent(
|
|
55
97
|
projectSourceDirectory: string = path.join(process.cwd(), 'src'),
|
|
56
98
|
) {
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
neo4j,
|
|
13
13
|
visualizeDependencies,
|
|
14
14
|
generateIntegrationGraphSchemaCommand,
|
|
15
|
+
generateIngestionSourcesConfigCommand,
|
|
15
16
|
troubleshootLocalExecution,
|
|
16
17
|
} from './commands';
|
|
17
18
|
|
|
@@ -28,5 +29,6 @@ export function createCli() {
|
|
|
28
29
|
.addCommand(neo4j())
|
|
29
30
|
.addCommand(visualizeDependencies())
|
|
30
31
|
.addCommand(generateIntegrationGraphSchemaCommand())
|
|
32
|
+
.addCommand(generateIngestionSourcesConfigCommand())
|
|
31
33
|
.addCommand(troubleshootLocalExecution());
|
|
32
34
|
}
|