@jupiterone/integration-sdk-cli 12.8.1 → 12.8.3

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 (154) hide show
  1. package/dist/src/bocchi/actions/steps.d.ts +4 -0
  2. package/dist/src/bocchi/actions/steps.js +20 -0
  3. package/dist/src/bocchi/actions/steps.js.map +1 -0
  4. package/dist/src/bocchi/bocchi.d.ts +1 -0
  5. package/dist/src/bocchi/bocchi.js +260 -0
  6. package/dist/src/bocchi/bocchi.js.map +1 -0
  7. package/dist/src/bocchi/templates/partials/directRelationships.hbs +26 -0
  8. package/dist/src/bocchi/templates/partials/mappedRelationships.hbs +36 -0
  9. package/dist/src/bocchi/templates/partials/stepMap.hbs +47 -0
  10. package/dist/src/bocchi/templates/steps/child-singleton.ts.hbs +47 -0
  11. package/dist/src/bocchi/templates/steps/fetch-child-entities.ts.hbs +58 -0
  12. package/dist/src/bocchi/templates/steps/fetch-entities.ts.hbs +47 -0
  13. package/dist/src/bocchi/templates/steps/index.test.ts.hbs +41 -0
  14. package/dist/src/bocchi/templates/steps/singleton.ts.hbs +45 -0
  15. package/dist/src/bocchi/templates/steps/spec.ts.hbs +73 -0
  16. package/dist/src/bocchi/templates/top-level/.env.example.hbs +3 -0
  17. package/dist/src/bocchi/templates/top-level/.eslintignore.hbs +1 -0
  18. package/dist/src/bocchi/templates/top-level/.eslintrc.hbs +6 -0
  19. package/dist/src/bocchi/templates/top-level/.github/workflows/build.yml.hbs +29 -0
  20. package/dist/src/bocchi/templates/top-level/.github/workflows/questions.yml.hbs +40 -0
  21. package/dist/src/bocchi/templates/top-level/.gitignore.hbs +8 -0
  22. package/dist/src/bocchi/templates/top-level/.node-version.hbs +1 -0
  23. package/dist/src/bocchi/templates/top-level/.prettierignore.hbs +6 -0
  24. package/dist/src/bocchi/templates/top-level/CHANGELOG.md.hbs +9 -0
  25. package/dist/src/bocchi/templates/top-level/CODEOWNERS.hbs +3 -0
  26. package/dist/src/bocchi/templates/top-level/Dockerfile.hbs +25 -0
  27. package/dist/src/bocchi/templates/top-level/LICENSE.hbs +373 -0
  28. package/dist/src/bocchi/templates/top-level/README.md.hbs +114 -0
  29. package/dist/src/bocchi/templates/top-level/docs/development.md.hbs +28 -0
  30. package/dist/src/bocchi/templates/top-level/docs/jupiterone.md.hbs +1 -0
  31. package/dist/src/bocchi/templates/top-level/docs/spec/index.ts.hbs +14 -0
  32. package/dist/src/bocchi/templates/top-level/husky.config.js.hbs +1 -0
  33. package/dist/src/bocchi/templates/top-level/jest.config.js.hbs +1 -0
  34. package/dist/src/bocchi/templates/top-level/jupiterone/questions/questions.yaml.hbs +16 -0
  35. package/dist/src/bocchi/templates/top-level/lint-staged.config.js.hbs +1 -0
  36. package/dist/src/bocchi/templates/top-level/package.json.hbs +49 -0
  37. package/dist/src/bocchi/templates/top-level/prettier.config.js.hbs +1 -0
  38. package/dist/src/bocchi/templates/top-level/src/client.ts.hbs +116 -0
  39. package/dist/src/bocchi/templates/top-level/src/config.ts.hbs +41 -0
  40. package/dist/src/bocchi/templates/top-level/src/index.test.ts.hbs +6 -0
  41. package/dist/src/bocchi/templates/top-level/src/index.ts.hbs +14 -0
  42. package/dist/src/bocchi/templates/top-level/src/steps/constants.ts.hbs +60 -0
  43. package/dist/src/bocchi/templates/top-level/src/steps/converters.ts.hbs +37 -0
  44. package/dist/src/bocchi/templates/top-level/src/steps/index.ts.hbs +13 -0
  45. package/dist/src/bocchi/templates/top-level/src/steps/types.ts.hbs +6 -0
  46. package/dist/src/bocchi/templates/top-level/test/README.md.hbs +5 -0
  47. package/dist/src/bocchi/templates/top-level/test/config.ts.hbs +30 -0
  48. package/dist/src/bocchi/templates/top-level/test/recording.ts.hbs +74 -0
  49. package/dist/src/bocchi/templates/top-level/tsconfig.dist.json.hbs +13 -0
  50. package/dist/src/bocchi/templates/top-level/tsconfig.json.hbs +7 -0
  51. package/dist/src/bocchi/utils/types.d.ts +98 -0
  52. package/dist/src/bocchi/utils/types.js +10 -0
  53. package/dist/src/bocchi/utils/types.js.map +1 -0
  54. package/dist/src/commands/bocchi.d.ts +1 -0
  55. package/dist/src/commands/bocchi.js +29 -0
  56. package/dist/src/commands/bocchi.js.map +1 -0
  57. package/dist/src/commands/collect.js.map +1 -1
  58. package/dist/src/commands/diff.js.map +1 -1
  59. package/dist/src/commands/document.js.map +1 -1
  60. package/dist/src/commands/generate-ingestion-sources-config.js.map +1 -1
  61. package/dist/src/commands/generate-integration-graph-schema.js.map +1 -1
  62. package/dist/src/commands/index.d.ts +1 -0
  63. package/dist/src/commands/index.js +1 -0
  64. package/dist/src/commands/index.js.map +1 -1
  65. package/dist/src/commands/options.js.map +1 -1
  66. package/dist/src/commands/run.js.map +1 -1
  67. package/dist/src/commands/validate-question-file.js.map +1 -1
  68. package/dist/src/commands/visualize-types.js.map +1 -1
  69. package/dist/src/config.js.map +1 -1
  70. package/dist/src/generator/actions.d.ts +2 -1
  71. package/dist/src/generator/actions.js +5 -1
  72. package/dist/src/generator/actions.js.map +1 -1
  73. package/dist/src/generator/entitiesFlow.js.map +1 -1
  74. package/dist/src/generator/newIntegration.js.map +1 -1
  75. package/dist/src/generator/relationshipsFlow.js.map +1 -1
  76. package/dist/src/generator/stepsFlow.js.map +1 -1
  77. package/dist/src/generator/util.js.map +1 -1
  78. package/dist/src/index.js +2 -1
  79. package/dist/src/index.js.map +1 -1
  80. package/dist/src/log.js.map +1 -1
  81. package/dist/src/neo4j/neo4jGraphStore.js.map +1 -1
  82. package/dist/src/neo4j/neo4jUtilities.js.map +1 -1
  83. package/dist/src/neo4j/uploadToNeo4j.js.map +1 -1
  84. package/dist/src/neo4j/wipeNeo4j.js.map +1 -1
  85. package/dist/src/questions/managedQuestionFileValidator.js.map +1 -1
  86. package/dist/src/services/queryLanguage.js.map +1 -1
  87. package/dist/src/troubleshoot/utils.js.map +1 -1
  88. package/dist/src/utils/generateVisHTML.js.map +1 -1
  89. package/dist/src/utils/getSortedJupiterOneTypes.js.map +1 -1
  90. package/dist/src/visualization/createMappedRelationshipNodesAndEdges.js.map +1 -1
  91. package/dist/src/visualization/generateDependencyVisualization.js.map +1 -1
  92. package/dist/src/visualization/generateVisualization.js.map +1 -1
  93. package/dist/src/visualization/retrieveIntegrationData.js.map +1 -1
  94. package/dist/src/visualization/utils.js.map +1 -1
  95. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  96. package/package.json +7 -6
  97. package/src/bocchi/README.md +95 -0
  98. package/src/bocchi/actions/steps.ts +17 -0
  99. package/src/bocchi/bocchi.ts +311 -0
  100. package/src/bocchi/docs/template/README.md +140 -0
  101. package/src/bocchi/docs/template/authentication.md +100 -0
  102. package/src/bocchi/docs/template/examples/example.json +127 -0
  103. package/src/bocchi/docs/template/examples/signalSciences.json +128 -0
  104. package/src/bocchi/docs/template/steps.md +656 -0
  105. package/src/bocchi/templates/partials/directRelationships.hbs +26 -0
  106. package/src/bocchi/templates/partials/mappedRelationships.hbs +36 -0
  107. package/src/bocchi/templates/partials/stepMap.hbs +47 -0
  108. package/src/bocchi/templates/steps/child-singleton.ts.hbs +47 -0
  109. package/src/bocchi/templates/steps/fetch-child-entities.ts.hbs +58 -0
  110. package/src/bocchi/templates/steps/fetch-entities.ts.hbs +47 -0
  111. package/src/bocchi/templates/steps/index.test.ts.hbs +41 -0
  112. package/src/bocchi/templates/steps/singleton.ts.hbs +45 -0
  113. package/src/bocchi/templates/steps/spec.ts.hbs +73 -0
  114. package/src/bocchi/templates/top-level/.env.example.hbs +3 -0
  115. package/src/bocchi/templates/top-level/.eslintignore.hbs +1 -0
  116. package/src/bocchi/templates/top-level/.eslintrc.hbs +6 -0
  117. package/src/bocchi/templates/top-level/.github/workflows/build.yml.hbs +29 -0
  118. package/src/bocchi/templates/top-level/.github/workflows/questions.yml.hbs +40 -0
  119. package/src/bocchi/templates/top-level/.gitignore.hbs +8 -0
  120. package/src/bocchi/templates/top-level/.node-version.hbs +1 -0
  121. package/src/bocchi/templates/top-level/.prettierignore.hbs +6 -0
  122. package/src/bocchi/templates/top-level/CHANGELOG.md.hbs +9 -0
  123. package/src/bocchi/templates/top-level/CODEOWNERS.hbs +3 -0
  124. package/src/bocchi/templates/top-level/Dockerfile.hbs +25 -0
  125. package/src/bocchi/templates/top-level/LICENSE.hbs +373 -0
  126. package/src/bocchi/templates/top-level/README.md.hbs +114 -0
  127. package/src/bocchi/templates/top-level/docs/development.md.hbs +28 -0
  128. package/src/bocchi/templates/top-level/docs/jupiterone.md.hbs +1 -0
  129. package/src/bocchi/templates/top-level/docs/spec/index.ts.hbs +14 -0
  130. package/src/bocchi/templates/top-level/husky.config.js.hbs +1 -0
  131. package/src/bocchi/templates/top-level/jest.config.js.hbs +1 -0
  132. package/src/bocchi/templates/top-level/jupiterone/questions/questions.yaml.hbs +16 -0
  133. package/src/bocchi/templates/top-level/lint-staged.config.js.hbs +1 -0
  134. package/src/bocchi/templates/top-level/package.json.hbs +49 -0
  135. package/src/bocchi/templates/top-level/prettier.config.js.hbs +1 -0
  136. package/src/bocchi/templates/top-level/src/client.ts.hbs +116 -0
  137. package/src/bocchi/templates/top-level/src/config.ts.hbs +41 -0
  138. package/src/bocchi/templates/top-level/src/index.test.ts.hbs +6 -0
  139. package/src/bocchi/templates/top-level/src/index.ts.hbs +14 -0
  140. package/src/bocchi/templates/top-level/src/steps/constants.ts.hbs +60 -0
  141. package/src/bocchi/templates/top-level/src/steps/converters.ts.hbs +37 -0
  142. package/src/bocchi/templates/top-level/src/steps/index.ts.hbs +13 -0
  143. package/src/bocchi/templates/top-level/src/steps/types.ts.hbs +6 -0
  144. package/src/bocchi/templates/top-level/test/README.md.hbs +5 -0
  145. package/src/bocchi/templates/top-level/test/config.ts.hbs +30 -0
  146. package/src/bocchi/templates/top-level/test/recording.ts.hbs +74 -0
  147. package/src/bocchi/templates/top-level/tsconfig.dist.json.hbs +13 -0
  148. package/src/bocchi/templates/top-level/tsconfig.json.hbs +7 -0
  149. package/src/bocchi/utils/types.ts +106 -0
  150. package/src/commands/bocchi.ts +28 -0
  151. package/src/commands/generate-ingestion-sources-config.ts +4 -5
  152. package/src/commands/index.ts +1 -0
  153. package/src/generator/actions.ts +13 -1
  154. package/src/index.ts +3 -1
@@ -0,0 +1,116 @@
1
+ import { BaseAPIClient } from '@jupiterone/integration-sdk-http-client';
2
+ import { IntegrationConfig } from './config';
3
+ import {
4
+ {{#each template.steps}}
5
+ {{pascalCase entity.name}},
6
+ {{/each}}
7
+ } from './steps/types';
8
+ import { Entity, IntegrationLogger } from '@jupiterone/integration-sdk-core';
9
+
10
+ export type ResourceIteratee<T> = (each: T) => Promise<void> | void;
11
+
12
+ export class APIClient extends BaseAPIClient {
13
+ private authHeaders: Record<string, string>;
14
+
15
+ constructor(readonly config: IntegrationConfig, logger: IntegrationLogger) {
16
+ super({
17
+ baseUrl: '{{template.baseUrl}}',
18
+ logger,
19
+ {{#if template.tokenBucket}}
20
+ tokenBucket: {
21
+ maximumCapacity: {{template.tokenBucket.maximumCapacity}},
22
+ refillRate: {{template.tokenBucket.refillRate}}
23
+ }
24
+ {{/if}}
25
+ });
26
+ }
27
+
28
+ {{#if (isNotEndpointAuth template.authentication.strategy)}}
29
+ protected getAuthorizationHeaders(): Record<string, string> {
30
+ return { {{#each (sanitizeAuthObject template.authentication.authHeaders)}}'{{@key}}': `{{this}}`,{{/each}} };
31
+ }
32
+ {{else}}
33
+ protected async getAuthorizationHeaders(): Promise<Record<string, string>> {
34
+ const response = await (await this.retryableRequest(
35
+ `${this.baseUrl}{{template.authentication.params.path}}`,
36
+ {
37
+ authorize: false, // needed only for authentication call
38
+ headers: { {{#each (sanitizeAuthObject template.authentication.params.headers)}}'{{@key}}': `{{this}}`,{{/each}} },
39
+ method: '{{sanitizeHttpMethod template.authentication.params.method}}',
40
+ {{#if request.params}}
41
+ body: { {{#each (sanitizeAuthObject template.authentication.params.body)}}{{@key}}: {{this}},{{/each}} }
42
+ {{/if}}
43
+ }
44
+ )).json();
45
+ return { {{#each (sanitizeAuthObject template.authentication.authHeaders)}}'{{@key}}': `{{this}}`,{{/each}} }
46
+ }
47
+ {{/if}}
48
+
49
+ public async verifyAuthentication(): Promise<void> {
50
+ {{#if (isNotEndpointAuth template.authentication.strategy)}}
51
+ // TODO: implement
52
+ await new Promise(() => { throw new Error('Unimplemented: Pick an API call to use for validation!') });
53
+ {{else}}
54
+ await this.getAuthorizationHeaders();
55
+ {{/if}}
56
+ }
57
+ {{#each template.steps}}
58
+
59
+ {{#if (isSingletonRequest response.responseType)}}
60
+ public async get{{pascalCase entity.name}}({{#if parentAssociation}}parentEntity: Entity{{/if}}): Promise<{{pascalCase entity.name}}> {
61
+ const url = `${this.baseUrl}{{{sanitizeUrlPath request.urlTemplate}}}`;
62
+ const response = await (await this.retryableRequest(
63
+ url,
64
+ {
65
+ method: '{{sanitizeHttpMethod request.method}}',
66
+ {{#if request.params}}
67
+ body: { {{#each (sanitizeHttpBody request.params)}}'{{@key}}': `{{this}}`,{{/each}} }
68
+ {{/if}}
69
+ }
70
+ )).json();
71
+ return response.{{response.dataPath}} as {{pascalCase entity.name}};
72
+ }
73
+ {{else}}
74
+ public async iterate{{pascalCase entity.name}}s(
75
+ {{#if parentAssociation}}
76
+ parentEntity: Entity,
77
+ {{/if}}
78
+ iteratee: ResourceIteratee<{{pascalCase entity.name}}>
79
+ ): Promise<void> {
80
+ {{#if response.nextTokenPath}}
81
+ let nextToken: string | undefined;
82
+ do {
83
+ {{/if}}
84
+ const url = `${this.baseUrl}{{{sanitizeUrlPath request.urlTemplate}}}`;
85
+ const response = await (await this.retryableRequest(
86
+ url,
87
+ {
88
+ method: '{{sanitizeHttpMethod request.method}}',
89
+ {{#if request.params}}
90
+ body: { {{#each (sanitizeHttpBody request.params)}}'{{@key}}': `{{this}}`,{{/each}} }
91
+ {{/if}}
92
+ }
93
+ )).json();
94
+ const resources = response.{{response.dataPath}} as {{pascalCase entity.name}}[];
95
+ for (const resource of resources) {
96
+ await iteratee(resource);
97
+ }
98
+ {{#if response.nextTokenPath}}
99
+ nextToken = response.{{response.nextTokenPath}};
100
+ } while (nextToken);
101
+ {{/if}}
102
+ }
103
+ {{/if}}
104
+ {{/each}}
105
+ }
106
+
107
+ let client: APIClient | undefined;
108
+
109
+ export function createAPIClient(
110
+ config: IntegrationConfig,
111
+ logger: IntegrationLogger
112
+ ): APIClient {
113
+ return client
114
+ ? client
115
+ : new APIClient(config, logger);
116
+ }
@@ -0,0 +1,41 @@
1
+ {{#with template}}
2
+ import {
3
+ IntegrationExecutionContext,
4
+ IntegrationValidationError,
5
+ IntegrationInstanceConfigFieldMap,
6
+ } from '@jupiterone/integration-sdk-core';
7
+ import { createAPIClient } from './client';
8
+
9
+ export const instanceConfigFields = {
10
+ {{#each instanceConfigFields}}
11
+ {{@key}}: {
12
+ {{#if type}}type: '{{type}}',{{/if}}
13
+ {{#if mask}}mask: {{mask}},{{/if}}
14
+ {{#if optional}}optional: {{optional}},{{/if}}
15
+ },
16
+ {{/each}}
17
+ } satisfies IntegrationInstanceConfigFieldMap;
18
+
19
+ export interface IntegrationConfig {
20
+ {{#each instanceConfigFields}}
21
+ {{@key}}: {{configTypeToType type}};
22
+ {{/each}}
23
+ }
24
+
25
+ export async function validateInvocation(
26
+ context: IntegrationExecutionContext<IntegrationConfig>,
27
+ ) {
28
+ const { config } = context.instance;
29
+
30
+ {{#with (requiredConfig instanceConfigFields)}}
31
+ {{#if this.length}}
32
+ if ({{#each this}}!config.{{this}}{{#unless @last}} || {{/unless}}{{/each}}) {
33
+ throw new IntegrationValidationError('Config requires all of{{#each this}} {{this}}{{/each}}');
34
+ }
35
+ {{/if}}
36
+ {{/with}}
37
+
38
+ const apiClient = createAPIClient(config);
39
+ await apiClient.verifyAuthentication();
40
+ }
41
+ {{/with}}
@@ -0,0 +1,6 @@
1
+ import { invocationConfig as implementedConfig } from '.';
2
+ import { invocationConfig as specConfig } from '../docs/spec';
3
+
4
+ test('implemented integration should match spec', () => {
5
+ expect(implementedConfig).toImplementSpec(specConfig, { requireSpec: true });
6
+ });
@@ -0,0 +1,14 @@
1
+ import { IntegrationInvocationConfig } from '@jupiterone/integration-sdk-core';
2
+ import { integrationSteps } from './steps';
3
+ import {
4
+ validateInvocation,
5
+ IntegrationConfig,
6
+ instanceConfigFields,
7
+ } from './config';
8
+
9
+ export const invocationConfig: IntegrationInvocationConfig<IntegrationConfig> =
10
+ {
11
+ instanceConfigFields,
12
+ validateInvocation,
13
+ integrationSteps,
14
+ };
@@ -0,0 +1,60 @@
1
+ {{#with template}}
2
+ import {
3
+ RelationshipClass,
4
+ StepEntityMetadata,
5
+ StepRelationshipMetadata,
6
+ } from '@jupiterone/integration-sdk-core';
7
+
8
+ export const Steps = {
9
+ {{#each steps}}
10
+ {{constantCase id}}: '{{id}}',
11
+ {{#if directRelationships}}
12
+ {{#with (getDirectRelationships this)}}
13
+ {{#each this}}
14
+ BUILD_{{constantCase sourceStep.entity.name}}_{{constantCase targetStep.entity.name}}_RELATIONSHIPS: 'build-{{kebabCase sourceStep.entity.name}}-{{kebabCase targetStep.entity.name}}-relationships',
15
+ {{/each}}
16
+ {{/with}}
17
+ {{/if}}
18
+ {{#if mappedRelationships}}
19
+ BUILD_{{constantCase entity.name}}_MAPPED_RELATIONSHIPS: 'build-{{kebabCase entity.name}}-mapped-relationships',
20
+ {{/if}}
21
+ {{/each}}
22
+ } satisfies Record<string, string>;
23
+
24
+ export const Entities = {
25
+ {{#each steps}}
26
+ {{#with entity}}
27
+ {{constantCase name}}: {
28
+ resourceName: '{{name}}',
29
+ _type: '{{_type}}',
30
+ _class: ['{{_class}}'],
31
+ },
32
+ {{/with}}
33
+ {{/each}}
34
+ } satisfies Record<string, StepEntityMetadata>;
35
+
36
+ export const Relationships = {
37
+ {{#each steps}}
38
+ {{#if parentAssociation}}
39
+ {{#with (getStepByType parentAssociation.parentEntityType)}}{{constantCase entity.name}}{{/with}}_{{constantCase parentAssociation.relationshipClass}}_{{constantCase entity.name}}: {
40
+ sourceType: Entities.{{#with (getStepByType parentAssociation.parentEntityType)}}{{constantCase entity.name}}{{/with}}._type,
41
+ _class: RelationshipClass.{{constantCase parentAssociation.relationshipClass}},
42
+ targetType: Entities.{{constantCase entity.name}}._type,
43
+ _type: '{{getRelationshipType parentAssociation.relationshipClass parentAssociation.parentEntityType entity._type}}',
44
+ },
45
+ {{/if}}
46
+ {{#if directRelationships}}
47
+ {{#with (getDirectRelationships this)}}
48
+ {{#each this}}
49
+ {{constantCase sourceStep.entity.name}}_{{constantCase relationshipClass}}_{{constantCase targetStep.entity.name}}: {
50
+ sourceType: Entities.{{constantCase sourceStep.entity.name}}._type,
51
+ _class: RelationshipClass.{{constantCase relationshipClass}},
52
+ targetType: Entities.{{constantCase targetStep.entity.name}}._type,
53
+ _type: '{{getRelationshipType relationshipClass sourceStep.entity._type targetStep.entity._type}}',
54
+ },
55
+ {{/each}}
56
+ {{/with}}
57
+ {{/if}}
58
+ {{/each}}
59
+ } satisfies Record<string, StepRelationshipMetadata>;
60
+ {{/with}}
@@ -0,0 +1,37 @@
1
+ {{#with template}}
2
+ import { createIntegrationEntity } from '@jupiterone/integration-sdk-core';
3
+ import { Entities } from './constants';
4
+ import {
5
+ {{#each steps}}
6
+ {{pascalCase entity.name}},
7
+ {{/each}}
8
+ } from './types';
9
+
10
+ {{#each steps}}
11
+ {{#with entity}}
12
+ export function create{{pascalCase name}}Entity(data: {{pascalCase name}}) {
13
+ return createIntegrationEntity({
14
+ entityData: {
15
+ source: data,
16
+ assign: {
17
+ _key: data.{{_keyPath}},
18
+ _type: Entities.{{constantCase name}}._type,
19
+ _class: Entities.{{constantCase name}}._class,
20
+ {{#if staticFields}}
21
+ {{#each staticFields}}
22
+ {{@key}}: {{{escape this}}},
23
+ {{/each}}
24
+ {{/if}}
25
+ {{#if fieldMappings}}
26
+ {{#each fieldMappings}}
27
+ {{@key}}: data.{{this}},
28
+ {{/each}}
29
+ {{/if}}
30
+ },
31
+ },
32
+ });
33
+ }
34
+ {{/with}}
35
+
36
+ {{/each}}
37
+ {{/with}}
@@ -0,0 +1,13 @@
1
+ {{#with template}}
2
+ {{#each steps}}
3
+ import { {{camelCase entity.name}}Steps } from './{{id}}';
4
+ {{/each}}
5
+
6
+ const integrationSteps = [
7
+ {{#each steps}}
8
+ ...{{camelCase entity.name}}Steps,
9
+ {{/each}}
10
+ ];
11
+
12
+ export { integrationSteps };
13
+ {{/with}}
@@ -0,0 +1,6 @@
1
+ {{#with template}}
2
+ {{#each steps}}
3
+ export type {{pascalCase entity.name}} = any;
4
+
5
+ {{/each}}
6
+ {{/with}}
@@ -0,0 +1,5 @@
1
+ # Testing Integrations
2
+
3
+ For information on testing integrations see:
4
+
5
+ [Testing Integrations](https://github.com/JupiterOne/sdk/blob/main/docs/integrations/testing.md)
@@ -0,0 +1,30 @@
1
+ import { IntegrationInvocationConfig } from '@jupiterone/integration-sdk-core';
2
+ import { StepTestConfig } from '@jupiterone/integration-sdk-testing';
3
+ import * as dotenv from 'dotenv';
4
+ import * as path from 'path';
5
+ import { invocationConfig } from '../src';
6
+ import { IntegrationConfig } from '../src/config';
7
+
8
+ if (process.env.LOAD_ENV) {
9
+ dotenv.config({
10
+ path: path.join(__dirname, '../.env'),
11
+ });
12
+ }
13
+
14
+ {{#each template.instanceConfigFields}}
15
+ const DEFAULT_{{constantCase @key}} = '';
16
+ {{/each}}
17
+
18
+ export const integrationConfig: IntegrationConfig = {
19
+ {{#each template.instanceConfigFields}}
20
+ {{camelCase @key}}: process.env.{{constantCase @key}} ?? DEFAULT_{{constantCase @key}},
21
+ {{/each}}
22
+ };
23
+
24
+ export function buildStepTestConfigForStep(stepId: string): StepTestConfig {
25
+ return {
26
+ stepId,
27
+ instanceConfig: integrationConfig,
28
+ invocationConfig: invocationConfig as IntegrationInvocationConfig,
29
+ };
30
+ }
@@ -0,0 +1,74 @@
1
+ import {
2
+ setupRecording,
3
+ Recording,
4
+ SetupRecordingInput,
5
+ mutations,
6
+ } from '@jupiterone/integration-sdk-testing';
7
+
8
+ export { Recording };
9
+
10
+ export function setupProjectRecording(
11
+ input: Omit<SetupRecordingInput, 'mutateEntry'>,
12
+ ): Recording {
13
+ return setupRecording({
14
+ ...input,
15
+ redactedRequestHeaders: ['Authorization'],
16
+ redactedResponseHeaders: ['set-cookie'],
17
+ mutateEntry: mutations.unzipGzippedRecordingEntry,
18
+ /*mutateEntry: (entry) => {
19
+ redact(entry);
20
+ },*/
21
+ });
22
+ }
23
+
24
+ // a more sophisticated redaction example below:
25
+
26
+ /*
27
+ function getRedactedOAuthResponse() {
28
+ return {
29
+ access_token: '[REDACTED]',
30
+ expires_in: 9999,
31
+ token_type: 'Bearer',
32
+ };
33
+ }
34
+
35
+ function redact(entry): void {
36
+ if (entry.request.postData) {
37
+ entry.request.postData.text = '[REDACTED]';
38
+ }
39
+
40
+ if (!entry.response.content.text) {
41
+ return;
42
+ }
43
+
44
+ //let's unzip the entry so we can modify it
45
+ mutations.unzipGzippedRecordingEntry(entry);
46
+
47
+ //we can just get rid of all response content if this was the token call
48
+ const requestUrl = entry.request.url;
49
+ if (requestUrl.match(/oauth\/token/)) {
50
+ entry.response.content.text = JSON.stringify(getRedactedOAuthResponse());
51
+ return;
52
+ }
53
+
54
+ //if it wasn't a token call, parse the response text, removing any carriage returns or newlines
55
+ const responseText = entry.response.content.text;
56
+ const parsedResponseText = JSON.parse(responseText.replace(/\r?\n|\r/g, ''));
57
+
58
+ //now we can modify the returned object as desired
59
+ //in this example, if the return text is an array of objects that have the 'tenant' property...
60
+ if (parsedResponseText[0]?.tenant) {
61
+ for (let i = 0; i < parsedResponseText.length; i++) {
62
+ parsedResponseText[i].client_secret = '[REDACTED]';
63
+ parsedResponseText[i].jwt_configuration = '[REDACTED]';
64
+ parsedResponseText[i].signing_keys = '[REDACTED]';
65
+ parsedResponseText[i].encryption_key = '[REDACTED]';
66
+ parsedResponseText[i].addons = '[REDACTED]';
67
+ parsedResponseText[i].client_metadata = '[REDACTED]';
68
+ parsedResponseText[i].mobile = '[REDACTED]';
69
+ parsedResponseText[i].native_social_login = '[REDACTED]';
70
+ }
71
+ }
72
+
73
+ entry.response.content.text = JSON.stringify(parsedResponseText);
74
+ } */
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src", "package.json"],
4
+ "exclude": [
5
+ "dist",
6
+ "**/*.test.ts",
7
+ "**/*.test.js",
8
+ "**/*/__tests__/**/*.ts",
9
+ "**/*/__tests__/**/*.js",
10
+ "**/*/__mocks__/**/*.ts",
11
+ "**/*/__mocks__/**/*.js"
12
+ ]
13
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./node_modules/@jupiterone/integration-sdk-dev-tools/config/typescript",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "exclude": ["dist"]
7
+ }
@@ -0,0 +1,106 @@
1
+ import { RelationshipClass } from '@jupiterone/integration-sdk-core';
2
+
3
+ export const StepType = {
4
+ SINGLETON: 'singleton',
5
+ CHILD_SINGLETON: 'child-singleton',
6
+ FETCH_ENTITIES: 'fetch-entities',
7
+ FETCH_CHILD_ENTITIES: 'fetch-child-entities',
8
+ };
9
+
10
+ export interface Template {
11
+ instanceConfigFields: {
12
+ [propertyName: string]: IntegrationConfigProperty;
13
+ };
14
+ authentication: EndpointAuthentication | ConfigFieldAuthentication;
15
+ baseUrl: string;
16
+ tokenBucket?: {
17
+ maximumCapacity: number;
18
+ refillRate: number;
19
+ };
20
+ steps: Step[];
21
+ }
22
+
23
+ interface IntegrationConfigProperty {
24
+ type?: 'string' | 'json' | 'boolean';
25
+ mask?: Boolean;
26
+ optional?: Boolean;
27
+ }
28
+
29
+ interface EndpointAuthentication {
30
+ strategy: 'endpoint';
31
+ params: {
32
+ path: string;
33
+ method: 'GET' | 'POST';
34
+ body?: Record<string, any>;
35
+ headers?: Record<string, string>;
36
+ };
37
+ authHeaders: AuthenticationHeaders;
38
+ }
39
+
40
+ /**
41
+ * Authentication headers to be used in API requests.
42
+ * The `Authorization` header is required, and is typically
43
+ * a bearer token or API key.
44
+ */
45
+ interface AuthenticationHeaders {
46
+ Authorization: string;
47
+ [headerName: string]: string;
48
+ }
49
+
50
+ interface ConfigFieldAuthentication {
51
+ strategy: 'configField';
52
+ authHeaders: {
53
+ [headerName: string]: string;
54
+ };
55
+ }
56
+
57
+ export interface Step {
58
+ id: string;
59
+ name: string;
60
+ entity: {
61
+ name: string;
62
+ _type: string;
63
+ _class: string | Array<string>;
64
+ _keyPath: string;
65
+ fieldMappings?: {
66
+ [entityProperty: string]: string;
67
+ };
68
+ staticFields?: {
69
+ [entityProperty: string]: string;
70
+ };
71
+ };
72
+ parentAssociation?: {
73
+ parentEntityType: string;
74
+ relationshipClass: string;
75
+ };
76
+ request: {
77
+ urlTemplate: string;
78
+ method?: 'GET' | 'POST';
79
+ params?: Record<string, any>;
80
+ };
81
+ response: {
82
+ dataPath: string;
83
+ responseType: 'SINGLETON' | 'LIST';
84
+ nextTokenPath?: string;
85
+ };
86
+ directRelationships?: {
87
+ targetKey: string;
88
+ targetType: string;
89
+ _class: RelationshipClass;
90
+ direction: 'FORWARD' | 'REVERSE';
91
+ }[];
92
+ mappedRelationships?: {
93
+ _class: RelationshipClass;
94
+ direction: 'FORWARD' | 'REVERSE';
95
+ mappings:
96
+ | {
97
+ sourceProperty: string;
98
+ targetProperty: string;
99
+ }
100
+ | {
101
+ targetValue: string;
102
+ targetProperty: string;
103
+ };
104
+ };
105
+ dependsOn?: string[];
106
+ }
@@ -0,0 +1,28 @@
1
+ import path from 'path';
2
+ import { createCommand } from 'commander';
3
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
4
+
5
+ export function bocchi() {
6
+ return createCommand('bocchi')
7
+ .description('New graph project generator')
8
+ .action(async (cmdOpts) => {
9
+ const Plop = await dynamicImport('plop');
10
+ const configPath = path.resolve(
11
+ path.join(__dirname, '../bocchi/bocchi.js'),
12
+ );
13
+ Plop.Plop.prepare(
14
+ {
15
+ cwd: process.cwd(),
16
+ configPath,
17
+ },
18
+ (env) =>
19
+ Plop.Plop.execute(env, (env) => {
20
+ const options = {
21
+ ...env,
22
+ dest: process.cwd(),
23
+ };
24
+ return Plop.run(options, undefined, true);
25
+ }),
26
+ );
27
+ });
28
+ }
@@ -102,11 +102,10 @@ export function generateIngestionSourcesConfig<
102
102
  return;
103
103
  }
104
104
  // Get the dependent steps for the given matchedIntegrationStepIds
105
- const childIngestionSources = integrationSteps.filter(
106
- (step) =>
107
- step.dependsOn?.some((value) =>
108
- matchedIntegrationStepIds.includes(value),
109
- ),
105
+ const childIngestionSources = integrationSteps.filter((step) =>
106
+ step.dependsOn?.some((value) =>
107
+ matchedIntegrationStepIds.includes(value),
108
+ ),
110
109
  );
111
110
  // Generate ingestionConfig with the childIngestionSources
112
111
  newIngestionConfig[key] = {
@@ -12,3 +12,4 @@ export * from './generate-integration-graph-schema';
12
12
  export * from './troubleshoot';
13
13
  export * from './generate-ingestion-sources-config';
14
14
  export * from './generate';
15
+ export * from './bocchi';
@@ -37,6 +37,10 @@ async function yarnLint(_answers, config, _plop) {
37
37
 
38
38
  // NPM commands
39
39
 
40
+ async function j1Document(_answers, config, _plop) {
41
+ return spawnCommand(config, 'npm exec', ['j1-integration', 'document']);
42
+ }
43
+
40
44
  async function npmInstall(_answers, config, _plop) {
41
45
  return spawnCommand(config, 'npm', ['install']);
42
46
  }
@@ -49,4 +53,12 @@ async function npmLint(_answers, config, _plop) {
49
53
  return spawnCommand(config, 'npm', ['run lint']);
50
54
  }
51
55
 
52
- export { yarnInstall, yarnFormat, yarnLint, npmInstall, npmFormat, npmLint };
56
+ export {
57
+ yarnInstall,
58
+ yarnFormat,
59
+ yarnLint,
60
+ npmInstall,
61
+ npmFormat,
62
+ npmLint,
63
+ j1Document,
64
+ };
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  generateIngestionSourcesConfigCommand,
16
16
  troubleshootLocalExecution,
17
17
  generate,
18
+ bocchi,
18
19
  } from './commands';
19
20
 
20
21
  export function createCli() {
@@ -32,5 +33,6 @@ export function createCli() {
32
33
  .addCommand(generateIntegrationGraphSchemaCommand())
33
34
  .addCommand(generateIngestionSourcesConfigCommand())
34
35
  .addCommand(troubleshootLocalExecution())
35
- .addCommand(generate());
36
+ .addCommand(generate())
37
+ .addCommand(bocchi());
36
38
  }