@unito/integration-cli 0.55.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 (150) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +32 -0
  3. package/bin/run +11 -0
  4. package/bin/run.cmd +3 -0
  5. package/dist/.eslintrc.d.ts +10 -0
  6. package/dist/.eslintrc.js +20 -0
  7. package/dist/integrationGenerator/errors.d.ts +2 -0
  8. package/dist/integrationGenerator/errors.js +6 -0
  9. package/dist/integrationGenerator/index.d.ts +2 -0
  10. package/dist/integrationGenerator/index.js +5 -0
  11. package/dist/integrationGenerator/integrationBoilerplate/.dockerignore +3 -0
  12. package/dist/integrationGenerator/integrationBoilerplate/.eslintrc.js +74 -0
  13. package/dist/integrationGenerator/integrationBoilerplate/.nvmrc +1 -0
  14. package/dist/integrationGenerator/integrationBoilerplate/.prettierignore +1 -0
  15. package/dist/integrationGenerator/integrationBoilerplate/.prettierrc +7 -0
  16. package/dist/integrationGenerator/integrationBoilerplate/.unito.json +1 -0
  17. package/dist/integrationGenerator/integrationBoilerplate/Dockerfile +38 -0
  18. package/dist/integrationGenerator/integrationBoilerplate/README.md +21 -0
  19. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.dockerignore +3 -0
  20. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.eslintrc.js +74 -0
  21. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.nvmrc +1 -0
  22. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.prettierignore +1 -0
  23. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.prettierrc +7 -0
  24. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/.unito.json +1 -0
  25. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/Dockerfile +38 -0
  26. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/README.md +21 -0
  27. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/package.json +43 -0
  28. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/index.ts +94 -0
  29. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/logger.ts +55 -0
  30. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/middlewares/additionalLoggingContext.ts +22 -0
  31. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/middlewares/correlationId.ts +13 -0
  32. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/middlewares/credentials.ts +38 -0
  33. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/request.ts +59 -0
  34. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/routes/index.ts +11 -0
  35. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/routes/me.ts +15 -0
  36. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/src/routes/root.ts +12 -0
  37. package/dist/integrationGenerator/integrationBoilerplate/integrationBoilerplate/tsconfig.json +37 -0
  38. package/dist/integrationGenerator/integrationBoilerplate/package.json +43 -0
  39. package/dist/integrationGenerator/integrationBoilerplate/src/index.ts +90 -0
  40. package/dist/integrationGenerator/integrationBoilerplate/src/logger.ts +37 -0
  41. package/dist/integrationGenerator/integrationBoilerplate/src/middlewares/correlationId.ts +18 -0
  42. package/dist/integrationGenerator/integrationBoilerplate/src/middlewares/credentials.ts +38 -0
  43. package/dist/integrationGenerator/integrationBoilerplate/src/request.ts +59 -0
  44. package/dist/integrationGenerator/integrationBoilerplate/src/routes/index.ts +11 -0
  45. package/dist/integrationGenerator/integrationBoilerplate/src/routes/me.ts +15 -0
  46. package/dist/integrationGenerator/integrationBoilerplate/src/routes/root.ts +12 -0
  47. package/dist/integrationGenerator/integrationBoilerplate/tsconfig.json +37 -0
  48. package/dist/integrationGenerator/src/index.d.ts +1 -0
  49. package/dist/integrationGenerator/src/index.js +5 -0
  50. package/dist/integrationGenerator/src/resources/index.d.ts +1 -0
  51. package/dist/integrationGenerator/src/resources/index.js +5 -0
  52. package/dist/integrationGenerator/src/resources/integration.d.ts +9 -0
  53. package/dist/integrationGenerator/src/resources/integration.js +60 -0
  54. package/dist/integrationGenerator/test/resources/integration.test.d.ts +1 -0
  55. package/dist/integrationGenerator/test/resources/integration.test.js +51 -0
  56. package/dist/schemas/authorization.json +204 -0
  57. package/dist/schemas/automation.json +81 -0
  58. package/dist/schemas/configuration.json +89 -0
  59. package/dist/scripts/generateTypes.d.ts +8 -0
  60. package/dist/scripts/generateTypes.js +44 -0
  61. package/dist/src/baseCommand.d.ts +14 -0
  62. package/dist/src/baseCommand.js +39 -0
  63. package/dist/src/commands/activity.d.ts +12 -0
  64. package/dist/src/commands/activity.js +75 -0
  65. package/dist/src/commands/dev.d.ts +15 -0
  66. package/dist/src/commands/dev.js +123 -0
  67. package/dist/src/commands/encrypt.d.ts +11 -0
  68. package/dist/src/commands/encrypt.js +50 -0
  69. package/dist/src/commands/init.d.ts +10 -0
  70. package/dist/src/commands/init.js +51 -0
  71. package/dist/src/commands/invite.d.ts +11 -0
  72. package/dist/src/commands/invite.js +71 -0
  73. package/dist/src/commands/login.d.ts +11 -0
  74. package/dist/src/commands/login.js +76 -0
  75. package/dist/src/commands/oauth2.d.ts +10 -0
  76. package/dist/src/commands/oauth2.js +99 -0
  77. package/dist/src/commands/publish.d.ts +28 -0
  78. package/dist/src/commands/publish.js +302 -0
  79. package/dist/src/commands/test.d.ts +9 -0
  80. package/dist/src/commands/test.js +165 -0
  81. package/dist/src/commands/upgrade.d.ts +7 -0
  82. package/dist/src/commands/upgrade.js +88 -0
  83. package/dist/src/configurationTypes.d.ts +209 -0
  84. package/dist/src/configurationTypes.js +49 -0
  85. package/dist/src/errors.d.ts +38 -0
  86. package/dist/src/errors.js +159 -0
  87. package/dist/src/hooks/init/displayLogo.d.ts +3 -0
  88. package/dist/src/hooks/init/displayLogo.js +37 -0
  89. package/dist/src/index.d.ts +1 -0
  90. package/dist/src/index.js +5 -0
  91. package/dist/src/oauth2Helper/oauth2Helper.d.ts +63 -0
  92. package/dist/src/oauth2Helper/oauth2Helper.js +235 -0
  93. package/dist/src/oauth2Helper/types.d.ts +22 -0
  94. package/dist/src/oauth2Helper/types.js +2 -0
  95. package/dist/src/resources/configuration.d.ts +30 -0
  96. package/dist/src/resources/configuration.js +191 -0
  97. package/dist/src/resources/decryption.d.ts +5 -0
  98. package/dist/src/resources/decryption.js +62 -0
  99. package/dist/src/resources/fileSystem.d.ts +2 -0
  100. package/dist/src/resources/fileSystem.js +22 -0
  101. package/dist/src/resources/globalConfiguration.d.ts +13 -0
  102. package/dist/src/resources/globalConfiguration.js +44 -0
  103. package/dist/src/resources/integrations.d.ts +2 -0
  104. package/dist/src/resources/integrations.js +17 -0
  105. package/dist/src/resources/integrationsPlatform.d.ts +2 -0
  106. package/dist/src/resources/integrationsPlatform.js +33 -0
  107. package/dist/src/services/integrationsPlatform.d.ts +36 -0
  108. package/dist/src/services/integrationsPlatform.js +162 -0
  109. package/dist/src/services/oauth2Helper.d.ts +3 -0
  110. package/dist/src/services/oauth2Helper.js +34 -0
  111. package/dist/test/commands/activity.test.d.ts +1 -0
  112. package/dist/test/commands/activity.test.js +62 -0
  113. package/dist/test/commands/dev.test.d.ts +1 -0
  114. package/dist/test/commands/dev.test.js +139 -0
  115. package/dist/test/commands/encrypt.test.d.ts +1 -0
  116. package/dist/test/commands/encrypt.test.js +73 -0
  117. package/dist/test/commands/init.test.d.ts +1 -0
  118. package/dist/test/commands/init.test.js +45 -0
  119. package/dist/test/commands/invite.test.d.ts +1 -0
  120. package/dist/test/commands/invite.test.js +56 -0
  121. package/dist/test/commands/login.test.d.ts +1 -0
  122. package/dist/test/commands/login.test.js +90 -0
  123. package/dist/test/commands/oauth2.test.d.ts +1 -0
  124. package/dist/test/commands/oauth2.test.js +104 -0
  125. package/dist/test/commands/publish.test.d.ts +1 -0
  126. package/dist/test/commands/publish.test.js +429 -0
  127. package/dist/test/commands/test.test.d.ts +1 -0
  128. package/dist/test/commands/test.test.js +171 -0
  129. package/dist/test/commands/upgrade.test.d.ts +1 -0
  130. package/dist/test/commands/upgrade.test.js +47 -0
  131. package/dist/test/errors.test.d.ts +1 -0
  132. package/dist/test/errors.test.js +96 -0
  133. package/dist/test/helpers/init.d.ts +1 -0
  134. package/dist/test/helpers/init.js +6 -0
  135. package/dist/test/mocha.hooks.d.ts +2 -0
  136. package/dist/test/mocha.hooks.js +37 -0
  137. package/dist/test/oauth2Helper/oauth2Helper.test.d.ts +1 -0
  138. package/dist/test/oauth2Helper/oauth2Helper.test.js +150 -0
  139. package/dist/test/resources/configuration.test.d.ts +1 -0
  140. package/dist/test/resources/configuration.test.js +586 -0
  141. package/dist/test/resources/decryption.test.d.ts +1 -0
  142. package/dist/test/resources/decryption.test.js +68 -0
  143. package/dist/test/resources/globalConfiguration.test.d.ts +1 -0
  144. package/dist/test/resources/globalConfiguration.test.js +32 -0
  145. package/dist/test/services/integrationsPlatform.test.d.ts +1 -0
  146. package/dist/test/services/integrationsPlatform.test.js +168 -0
  147. package/dist/test/services/oauth2Helper.test.d.ts +1 -0
  148. package/dist/test/services/oauth2Helper.test.js +85 -0
  149. package/oclif.manifest.json +423 -0
  150. package/package.json +98 -0
@@ -0,0 +1,38 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+
3
+ // This interface contains the different variables defined in the authorization methods
4
+ // of your integration (in the Integrations Platform registry).
5
+ //
6
+ // For example, if you add a "custom" authorization method in the registry with the variable "apiKey",
7
+ // you must add "apiKey: string" to this interface.
8
+ //
9
+ // For your convenience, this interface is initialized for an integration that can either have:
10
+ //
11
+ // * No authentication.
12
+ // * API key authentication (Bearer <accessToken>).
13
+ // * OAuth 2 authentication (Bearer <accessToken>).
14
+ //
15
+ export interface Credentials {
16
+ accessToken?: string;
17
+ }
18
+
19
+ export const extractCredentials = (req: Request, res: Response, next: NextFunction) => {
20
+ const credentialsHeader = req.header('X-Unito-Credentials');
21
+
22
+ let credentials: Credentials | null = null;
23
+
24
+ if (credentialsHeader) {
25
+ try {
26
+ credentials = JSON.parse(Buffer.from(credentialsHeader, 'base64').toString('utf8'));
27
+ } catch {
28
+ return res.status(400).json({ code: 400, message: 'Error parsing credentials' });
29
+ }
30
+ }
31
+
32
+ // You can add additional verifications here to make sure you received
33
+ // all the necessary information to authenticate a user.
34
+
35
+ res.locals.credentials = credentials;
36
+
37
+ next();
38
+ };
@@ -0,0 +1,59 @@
1
+ import { Credentials } from './middlewares/credentials';
2
+
3
+ export interface RequestOptions {
4
+ queryParams?: { [key: string]: string };
5
+ method?: 'POST' | 'GET' | 'PATCH';
6
+ body?: Record<string, unknown>;
7
+ credentials?: Credentials;
8
+ }
9
+
10
+ const apiUrl = 'https://path_to_your_api';
11
+
12
+ export async function get<T>(endpoint: string, options: RequestOptions): Promise<T> {
13
+ return fetchWrapper(endpoint, options);
14
+ }
15
+
16
+ export async function post<T>(endpoint: string, options: RequestOptions): Promise<T> {
17
+ return fetchWrapper<T>(endpoint, {
18
+ ...options,
19
+ method: 'POST',
20
+ });
21
+ }
22
+
23
+ export async function patch<T>(endpoint: string, options: RequestOptions): Promise<T> {
24
+ return fetchWrapper<T>(endpoint, {
25
+ ...options,
26
+ method: 'PATCH',
27
+ });
28
+ }
29
+
30
+ async function fetchWrapper<T>(endpoint: string, options: RequestOptions): Promise<T> {
31
+ let absoluteUrl = [apiUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
32
+
33
+ if (options.queryParams) {
34
+ absoluteUrl = `${absoluteUrl}?${new URLSearchParams(options.queryParams)}`;
35
+ }
36
+
37
+ const headers: { [key: string]: string } = {
38
+ 'Content-Type': 'application/json',
39
+ Accept: 'application/json',
40
+ };
41
+
42
+ if (options.credentials?.accessToken) {
43
+ headers['Authorization'] = `Bearer ${options.credentials?.accessToken}`;
44
+ }
45
+
46
+ const response = await fetch(absoluteUrl, {
47
+ method: options.method,
48
+ headers,
49
+ body: options.body ? JSON.stringify(options.body) : undefined,
50
+ });
51
+
52
+ if (response.status === 200) {
53
+ return (await response.json()) as T;
54
+ }
55
+
56
+ const textResult = await response.text();
57
+
58
+ throw new Error(textResult);
59
+ }
@@ -0,0 +1,11 @@
1
+ import { Router } from 'express';
2
+
3
+ import { router as rootRouter } from './root';
4
+ import { router as meRouter } from './me';
5
+
6
+ const router = Router();
7
+
8
+ router.use('/', rootRouter);
9
+ router.use('/me', meRouter);
10
+
11
+ export default router;
@@ -0,0 +1,15 @@
1
+ import { Request, Response, Router } from 'express';
2
+
3
+ import { CredentialAccount } from '@unito/integration-api';
4
+
5
+ export const router = Router();
6
+
7
+ router.get('/', async (_req: Request, res: Response<CredentialAccount>) => {
8
+ const account: CredentialAccount = {
9
+ id: 'me',
10
+ displayName: 'Me',
11
+ emails: [],
12
+ };
13
+
14
+ res.json(account);
15
+ });
@@ -0,0 +1,12 @@
1
+ import { Request, Response, Router } from 'express';
2
+
3
+ import { Item } from '@unito/integration-api';
4
+
5
+ export const router = Router();
6
+
7
+ router.get('/', (_req: Request, res: Response<Item>) => {
8
+ return res.send({
9
+ fields: {},
10
+ relations: [],
11
+ });
12
+ });
@@ -0,0 +1,37 @@
1
+ {
2
+ "ts-node": {
3
+ "logError": true
4
+ },
5
+ "compilerOptions": {
6
+ "allowJs": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "baseUrl": ".",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "emitDecoratorMetadata": true,
12
+ "esModuleInterop": true,
13
+ "experimentalDecorators": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "incremental": true,
16
+ "isolatedModules": false,
17
+ "lib": ["dom", "ES2022"],
18
+ "module": "commonjs",
19
+ "moduleResolution": "node",
20
+ "noImplicitAny": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUnusedLocals": true,
23
+ "outDir": "dist",
24
+ "pretty": true,
25
+ "resolveJsonModule": true,
26
+ "rootDir": ".",
27
+ "skipLibCheck": true,
28
+ "sourceMap": true,
29
+ "strict": true,
30
+ "strictFunctionTypes": true,
31
+ "strictNullChecks": true,
32
+ "strictPropertyInitialization": false,
33
+ "target": "ES2022"
34
+ },
35
+ "include": ["src/**/*"],
36
+ "exclude": ["node_modules"]
37
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "integration-boilerplate",
3
+ "version": "0.0.1",
4
+ "description": "Integration Boilerplate",
5
+ "scripts": {
6
+ "compile": "tsc",
7
+ "dev": "nodemon --watch \"src/**\" --ext ts --exec \"node --inspect --no-lazy -r ts-node/register/transpile-only src/index.ts\"",
8
+ "lint": "eslint --resolve-plugins-relative-to . --fix src --ext .ts && prettier --write src",
9
+ "ci:audit": "check-audit",
10
+ "ci:eslint": "eslint --resolve-plugins-relative-to . src --ext .ts",
11
+ "ci:lint": "npm run ci:prettier && npm run ci:eslint || (echo \"Please run eslint and/or prettier and commit the changes\" && exit 1)",
12
+ "ci:prettier": "prettier --cache --ignore-unknown --check src",
13
+ "ci:test": "NODE_ENV=test mocha"
14
+ },
15
+ "author": {
16
+ "name": "Unito",
17
+ "email": "hello@unito.io"
18
+ },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "license": "LicenseRef-LICENSE",
23
+ "dependencies": {
24
+ "@unito/integration-api": "^0.x",
25
+ "express": "^5.0.0-beta.1",
26
+ "uuid": "9.x"
27
+ },
28
+ "devDependencies": {
29
+ "@types/express": "4.x",
30
+ "@types/mocha": "10.x",
31
+ "@types/node": "18.x",
32
+ "@types/uuid": "9.x",
33
+ "@typescript-eslint/eslint-plugin": "5.x",
34
+ "@typescript-eslint/parser": "5.x",
35
+ "eslint": "8.x",
36
+ "mocha": "10.x",
37
+ "nodemon": "2.x",
38
+ "npm-audit-resolver": "^3.0.0-RC.0",
39
+ "prettier": "2.x",
40
+ "ts-node": "10.x",
41
+ "typescript": "5.x"
42
+ }
43
+ }
@@ -0,0 +1,90 @@
1
+ import express from 'express';
2
+ import { Error as APIError } from '@unito/integration-api';
3
+
4
+ import indexRouter from './routes/index';
5
+ import { extractCredentials } from './middlewares/credentials';
6
+ import { extractCorrelationId } from './middlewares/correlationId';
7
+ import { logger } from './logger';
8
+
9
+ // Express Server initialization
10
+ const app: express.Application = express();
11
+
12
+ // Parse query strings with https://github.com/ljharb/qs.
13
+ app.set('query parser', 'extended');
14
+
15
+ app.use(express.json());
16
+
17
+ // Must be one of the first handlers (to catch all the errors).
18
+ app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
19
+ if (req.originalUrl !== '/health') {
20
+ res.on('finish', function () {
21
+ const loggerLevel = res.statusCode >= 500 ? 'error' : 'info';
22
+
23
+ // eslint-disable-next-line
24
+ logger[loggerLevel](`${req.method} ${req.originalUrl} ${res.statusCode}`, res.locals.correlationId);
25
+ });
26
+ }
27
+
28
+ next();
29
+ });
30
+
31
+ // Extract and validate the credentials.
32
+ app.use(extractCredentials);
33
+
34
+ // Extract the correlation id.
35
+ app.use(extractCorrelationId);
36
+
37
+ // Load the routes.
38
+ app.use('/', indexRouter);
39
+
40
+ // Must be the (last - 1) handler.
41
+ app.use((err: Error, _req: express.Request, res: express.Response, next: express.NextFunction) => {
42
+ if (res.headersSent) {
43
+ return next(err);
44
+ }
45
+
46
+ const originalError: APIError = {
47
+ code: err.name,
48
+ message: err.message,
49
+ };
50
+
51
+ res.status(500).json({
52
+ code: '500',
53
+ message: 'Oops! Something went wrong',
54
+ originalError: originalError,
55
+ } as APIError);
56
+ });
57
+
58
+ // Must be the last handler.
59
+ app.use((req: express.Request, res: express.Response, _next: express.NextFunction) => {
60
+ const error: APIError = {
61
+ code: '404',
62
+ message: `Path ${req.path} not found.`,
63
+ };
64
+
65
+ res.status(404).json(error);
66
+ });
67
+
68
+ // eslint-disable-next-line
69
+ const instance = app.listen(process.env.PORT || 9200, () =>
70
+ console.log(`Server started on port ${process.env.PORT || 9200}.`),
71
+ );
72
+
73
+ // Trap exit signals.
74
+ ['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
75
+ process.once(signalType, async () => {
76
+ // eslint-disable-next-line
77
+ console.log(`Received termination signal ${signalType}. Exiting.`);
78
+
79
+ try {
80
+ if (instance) {
81
+ instance.close();
82
+ }
83
+ } catch (e) {
84
+ // eslint-disable-next-line
85
+ console.error('Failed to gracefully exit', e);
86
+ }
87
+
88
+ process.exit();
89
+ });
90
+ });
@@ -0,0 +1,37 @@
1
+ enum LogLevel {
2
+ ERROR = 'error',
3
+ WARN = 'warn',
4
+ INFO = 'info',
5
+ LOG = 'log',
6
+ DEBUG = 'debug',
7
+ }
8
+
9
+ export class Logger {
10
+ private send(logLevel: LogLevel, message: string, correlationId: string) {
11
+ console[logLevel](
12
+ // Datadog automatically parses JSON-formatted logs
13
+ JSON.stringify({
14
+ message: message,
15
+ correlation_id: correlationId,
16
+ }),
17
+ );
18
+ }
19
+
20
+ public log(message: string, correlationId: string) {
21
+ this.send(LogLevel.LOG, message, correlationId);
22
+ }
23
+ public error(message: string, correlationId: string) {
24
+ this.send(LogLevel.ERROR, message, correlationId);
25
+ }
26
+ public warn(message: string, correlationId: string) {
27
+ this.send(LogLevel.WARN, message, correlationId);
28
+ }
29
+ public info(message: string, correlationId: string) {
30
+ this.send(LogLevel.INFO, message, correlationId);
31
+ }
32
+ public debug(message: string, correlationId: string) {
33
+ this.send(LogLevel.DEBUG, message, correlationId);
34
+ }
35
+ }
36
+
37
+ export const logger = new Logger();
@@ -0,0 +1,18 @@
1
+ import express from 'express';
2
+ import * as uuid from 'uuid';
3
+
4
+ export const extractCorrelationId = (req: express.Request, res: express.Response, next: express.NextFunction) => {
5
+ const correlationIdHeader = req.header('X-Unito-Correlation-Id');
6
+
7
+ let correlationId;
8
+
9
+ if (correlationIdHeader) {
10
+ correlationId = correlationIdHeader;
11
+ } else {
12
+ correlationId = uuid.v4();
13
+ }
14
+
15
+ res.locals.correlationId = correlationId;
16
+
17
+ next();
18
+ };
@@ -0,0 +1,38 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+
3
+ // This interface contains the different variables defined in the authorization methods
4
+ // of your integration (in the Integrations Platform registry).
5
+ //
6
+ // For example, if you add a "custom" authorization method in the registry with the variable "apiKey",
7
+ // you must add "apiKey: string" to this interface.
8
+ //
9
+ // For your convenience, this interface is initialized for an integration that can either have:
10
+ //
11
+ // * No authentication.
12
+ // * API key authentication (Bearer <accessToken>).
13
+ // * OAuth 2 authentication (Bearer <accessToken>).
14
+ //
15
+ export interface Credentials {
16
+ accessToken?: string;
17
+ }
18
+
19
+ export const extractCredentials = (req: Request, res: Response, next: NextFunction) => {
20
+ const credentialsHeader = req.header('X-Unito-Credentials');
21
+
22
+ let credentials: Credentials | null = null;
23
+
24
+ if (credentialsHeader) {
25
+ try {
26
+ credentials = JSON.parse(Buffer.from(credentialsHeader, 'base64').toString('utf8'));
27
+ } catch {
28
+ return res.status(400).json({ code: 400, message: 'Error parsing credentials' });
29
+ }
30
+ }
31
+
32
+ // You can add additional verifications here to make sure you received
33
+ // all the necessary information to authenticate a user.
34
+
35
+ res.locals.credentials = credentials;
36
+
37
+ next();
38
+ };
@@ -0,0 +1,59 @@
1
+ import { Credentials } from './middlewares/credentials';
2
+
3
+ export interface RequestOptions {
4
+ queryParams?: { [key: string]: string };
5
+ method?: 'POST' | 'GET' | 'PATCH';
6
+ body?: Record<string, unknown>;
7
+ credentials?: Credentials;
8
+ }
9
+
10
+ const apiUrl = 'https://path_to_your_api';
11
+
12
+ export async function get<T>(endpoint: string, options: RequestOptions): Promise<T> {
13
+ return fetchWrapper(endpoint, options);
14
+ }
15
+
16
+ export async function post<T>(endpoint: string, options: RequestOptions): Promise<T> {
17
+ return fetchWrapper<T>(endpoint, {
18
+ ...options,
19
+ method: 'POST',
20
+ });
21
+ }
22
+
23
+ export async function patch<T>(endpoint: string, options: RequestOptions): Promise<T> {
24
+ return fetchWrapper<T>(endpoint, {
25
+ ...options,
26
+ method: 'PATCH',
27
+ });
28
+ }
29
+
30
+ async function fetchWrapper<T>(endpoint: string, options: RequestOptions): Promise<T> {
31
+ let absoluteUrl = [apiUrl, endpoint.charAt(0) === '/' ? endpoint.substring(1) : endpoint].join('/');
32
+
33
+ if (options.queryParams) {
34
+ absoluteUrl = `${absoluteUrl}?${new URLSearchParams(options.queryParams)}`;
35
+ }
36
+
37
+ const headers: { [key: string]: string } = {
38
+ 'Content-Type': 'application/json',
39
+ Accept: 'application/json',
40
+ };
41
+
42
+ if (options.credentials?.accessToken) {
43
+ headers['Authorization'] = `Bearer ${options.credentials?.accessToken}`;
44
+ }
45
+
46
+ const response = await fetch(absoluteUrl, {
47
+ method: options.method,
48
+ headers,
49
+ body: options.body ? JSON.stringify(options.body) : undefined,
50
+ });
51
+
52
+ if (response.status === 200) {
53
+ return (await response.json()) as T;
54
+ }
55
+
56
+ const textResult = await response.text();
57
+
58
+ throw new Error(textResult);
59
+ }
@@ -0,0 +1,11 @@
1
+ import { Router } from 'express';
2
+
3
+ import { router as rootRouter } from './root';
4
+ import { router as meRouter } from './me';
5
+
6
+ const router = Router();
7
+
8
+ router.use('/', rootRouter);
9
+ router.use('/me', meRouter);
10
+
11
+ export default router;
@@ -0,0 +1,15 @@
1
+ import { Request, Response, Router } from 'express';
2
+
3
+ import { CredentialAccount } from '@unito/integration-api';
4
+
5
+ export const router = Router();
6
+
7
+ router.get('/', async (_req: Request, res: Response<CredentialAccount>) => {
8
+ const account: CredentialAccount = {
9
+ id: 'me',
10
+ displayName: 'Me',
11
+ emails: [],
12
+ };
13
+
14
+ res.json(account);
15
+ });
@@ -0,0 +1,12 @@
1
+ import { Request, Response, Router } from 'express';
2
+
3
+ import { Item } from '@unito/integration-api';
4
+
5
+ export const router = Router();
6
+
7
+ router.get('/', (_req: Request, res: Response<Item>) => {
8
+ return res.send({
9
+ fields: {},
10
+ relations: [],
11
+ });
12
+ });
@@ -0,0 +1,37 @@
1
+ {
2
+ "ts-node": {
3
+ "logError": true
4
+ },
5
+ "compilerOptions": {
6
+ "allowJs": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "baseUrl": ".",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "emitDecoratorMetadata": true,
12
+ "esModuleInterop": true,
13
+ "experimentalDecorators": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "incremental": true,
16
+ "isolatedModules": false,
17
+ "lib": ["dom", "ES2022"],
18
+ "module": "commonjs",
19
+ "moduleResolution": "node",
20
+ "noImplicitAny": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUnusedLocals": true,
23
+ "outDir": "dist",
24
+ "pretty": true,
25
+ "resolveJsonModule": true,
26
+ "rootDir": ".",
27
+ "skipLibCheck": true,
28
+ "sourceMap": true,
29
+ "strict": true,
30
+ "strictFunctionTypes": true,
31
+ "strictNullChecks": true,
32
+ "strictPropertyInitialization": false,
33
+ "target": "ES2022"
34
+ },
35
+ "include": ["src/**/*"],
36
+ "exclude": ["node_modules"]
37
+ }
@@ -0,0 +1 @@
1
+ export * as Resources from './resources';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Resources = void 0;
4
+ const tslib_1 = require("tslib");
5
+ exports.Resources = tslib_1.__importStar(require("./resources"));
@@ -0,0 +1 @@
1
+ export * as Integration from './integration';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Integration = void 0;
4
+ const tslib_1 = require("tslib");
5
+ exports.Integration = tslib_1.__importStar(require("./integration"));
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Generates an integration skeleton with the specified name.
3
+ *
4
+ * @param {string} integrationName - The name of the integration.
5
+ * @param {string} [destinationPath] - The destination path where the integration skeleton will be generated. If not provided, a default path will be used.
6
+ * @returns {string} - The path of the generated integration skeleton.
7
+ * @throws {Error} - If an integration with the same name already exists.
8
+ */
9
+ export declare function generateIntegrationSkeleton(integrationName: string, destinationPath?: string): Promise<string>;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateIntegrationSkeleton = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const errors_1 = require("../../errors");
8
+ /**
9
+ * Generates an integration skeleton with the specified name.
10
+ *
11
+ * @param {string} integrationName - The name of the integration.
12
+ * @param {string} [destinationPath] - The destination path where the integration skeleton will be generated. If not provided, a default path will be used.
13
+ * @returns {string} - The path of the generated integration skeleton.
14
+ * @throws {Error} - If an integration with the same name already exists.
15
+ */
16
+ async function generateIntegrationSkeleton(integrationName, destinationPath) {
17
+ const srcPath = path.join(__dirname, `../../integrationBoilerplate`);
18
+ // istanbul ignore next
19
+ const destPath = destinationPath ?? path.join(__dirname, `../../outputIntegrations`);
20
+ const targetFolder = `${destPath}/${integrationName.toLowerCase().replace(/ /g, '_')}`;
21
+ if (!fs.existsSync(targetFolder)) {
22
+ await fs.promises.mkdir(targetFolder, { recursive: true });
23
+ await copyFilesAndFolders(srcPath, targetFolder, ['node_modules', 'dist']);
24
+ // The .npmrc file is created here because it will be excluded during the CLI package's publishing process by npm.
25
+ // This step is necessary to ensure that the correct registry and auth token settings are present
26
+ // when installing package dependencies.
27
+ const npmrcContent = [
28
+ '@unitoio:registry=https://npm.pkg.github.com/unitoio',
29
+ '//npm.pkg.github.com/:_authToken=${UNITO_GITHUB_PKG_TOKEN}',
30
+ 'engine-strict=true',
31
+ 'strict-ssl=true',
32
+ ].join('\n');
33
+ await fs.promises.writeFile(path.join(targetFolder, '.npmrc'), npmrcContent);
34
+ }
35
+ else {
36
+ throw new errors_1.AlreadyExistingIntegrationError(`Integration already exists with this name: ${integrationName}`);
37
+ }
38
+ return targetFolder;
39
+ }
40
+ exports.generateIntegrationSkeleton = generateIntegrationSkeleton;
41
+ async function copyFilesAndFolders(source, destination, excludedFolders) {
42
+ const files = await fs.promises.readdir(source);
43
+ for (const file of files) {
44
+ /* istanbul ignore next */
45
+ if (excludedFolders.includes(file)) {
46
+ continue;
47
+ }
48
+ const sourcePath = path.join(source, file);
49
+ const destPath = path.join(destination, file);
50
+ const stats = await fs.promises.stat(sourcePath);
51
+ /* istanbul ignore else */
52
+ if (stats.isFile()) {
53
+ await fs.promises.copyFile(sourcePath, destPath);
54
+ }
55
+ else if (stats.isDirectory()) {
56
+ await fs.promises.mkdir(destPath);
57
+ await copyFilesAndFolders(sourcePath, destPath, excludedFolders);
58
+ }
59
+ }
60
+ }