@seeka-labs/cli-apps 1.0.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 (60) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +1 -0
  3. package/dist/index.js +47 -0
  4. package/dist/init-templates/aws-lambda/.env.example +15 -0
  5. package/dist/init-templates/aws-lambda/.eslintrc.cjs +10 -0
  6. package/dist/init-templates/aws-lambda/.example.gitignore +49 -0
  7. package/dist/init-templates/aws-lambda/.gitlab-ci.yml +37 -0
  8. package/dist/init-templates/aws-lambda/.vscode/extensions.json +5 -0
  9. package/dist/init-templates/aws-lambda/.vscode/launch.json +20 -0
  10. package/dist/init-templates/aws-lambda/.vscode/settings.json +3 -0
  11. package/dist/init-templates/aws-lambda/.vscode/tasks.json +12 -0
  12. package/dist/init-templates/aws-lambda/README.md +75 -0
  13. package/dist/init-templates/aws-lambda/package.json +51 -0
  14. package/dist/init-templates/aws-lambda/scripts/ngrok.js +28 -0
  15. package/dist/init-templates/aws-lambda/src/index.ts +33 -0
  16. package/dist/init-templates/aws-lambda/src/lib/logging/index.ts +88 -0
  17. package/dist/init-templates/aws-lambda/src/lib/services/index.ts +41 -0
  18. package/dist/init-templates/aws-lambda/src/lib/state/redis/index.ts +64 -0
  19. package/dist/init-templates/aws-lambda/src/lib/state/seeka/installations.ts +67 -0
  20. package/dist/init-templates/aws-lambda/src/routes/seekaAppWebhook.ts +170 -0
  21. package/dist/init-templates/aws-lambda/tsconfig.json +31 -0
  22. package/dist/init-templates/azure-function/.eslintrc.cjs +10 -0
  23. package/dist/init-templates/azure-function/.example.gitignore +48 -0
  24. package/dist/init-templates/azure-function/.funcignore +17 -0
  25. package/dist/init-templates/azure-function/.gitlab-ci.yml +33 -0
  26. package/dist/init-templates/azure-function/.vscode/extensions.json +7 -0
  27. package/dist/init-templates/azure-function/.vscode/launch.json +13 -0
  28. package/dist/init-templates/azure-function/.vscode/settings.json +9 -0
  29. package/dist/init-templates/azure-function/.vscode/tasks.json +39 -0
  30. package/dist/init-templates/azure-function/README.md +102 -0
  31. package/dist/init-templates/azure-function/host.json +20 -0
  32. package/dist/init-templates/azure-function/local.settings.example.json +23 -0
  33. package/dist/init-templates/azure-function/package.json +44 -0
  34. package/dist/init-templates/azure-function/scripts/ngrok.js +28 -0
  35. package/dist/init-templates/azure-function/src/functions/pollingExample.ts +39 -0
  36. package/dist/init-templates/azure-function/src/functions/queueExample.ts +33 -0
  37. package/dist/init-templates/azure-function/src/functions/seekaAppWebhook.ts +200 -0
  38. package/dist/init-templates/azure-function/src/lib/jobs/index.ts +54 -0
  39. package/dist/init-templates/azure-function/src/lib/logging/index.ts +93 -0
  40. package/dist/init-templates/azure-function/src/lib/services/index.ts +41 -0
  41. package/dist/init-templates/azure-function/src/lib/state/redis/index.ts +64 -0
  42. package/dist/init-templates/azure-function/src/lib/state/seeka/installations.ts +67 -0
  43. package/dist/init-templates/azure-function/tsconfig.json +22 -0
  44. package/dist/init-templates/netlify-function/.env.example +18 -0
  45. package/dist/init-templates/netlify-function/.eslintrc.cjs +7 -0
  46. package/dist/init-templates/netlify-function/.example.gitignore +36 -0
  47. package/dist/init-templates/netlify-function/.vscode/launch.json +45 -0
  48. package/dist/init-templates/netlify-function/README.md +60 -0
  49. package/dist/init-templates/netlify-function/netlify.toml +7 -0
  50. package/dist/init-templates/netlify-function/package.json +38 -0
  51. package/dist/init-templates/netlify-function/src/api/example-job-background/index.ts +52 -0
  52. package/dist/init-templates/netlify-function/src/api/polling-example-job-scheduled/index.ts +46 -0
  53. package/dist/init-templates/netlify-function/src/api/seeka-app-webhook/index.ts +185 -0
  54. package/dist/init-templates/netlify-function/src/lib/jobs/index.ts +68 -0
  55. package/dist/init-templates/netlify-function/src/lib/logging/index.ts +91 -0
  56. package/dist/init-templates/netlify-function/src/lib/services/index.ts +41 -0
  57. package/dist/init-templates/netlify-function/src/lib/state/redis/index.ts +64 -0
  58. package/dist/init-templates/netlify-function/src/lib/state/seeka/installations.ts +67 -0
  59. package/dist/init-templates/netlify-function/tsconfig.json +25 -0
  60. package/package.json +48 -0
@@ -0,0 +1,88 @@
1
+ import { Context } from 'aws-lambda';
2
+ import * as winston from 'winston';
3
+
4
+ import { SeqTransport } from '@datalust/winston-seq';
5
+
6
+ import * as packageJson from '../../../package.json';
7
+
8
+ import type {
9
+ SeekaWebhookPayload, SeekaWebhookPayloadOfSeekaAppWebhookContext
10
+ } from '@seeka-labs/sdk-apps-server';
11
+
12
+ const loggerTransports: winston.transport[] = [
13
+ new winston.transports.Console({
14
+ level: process.env.LOGGING_LEVEL,
15
+ format: winston.format.combine(
16
+ winston.format.errors({ stack: true }),
17
+ winston.format.cli(),
18
+ winston.format.splat(),
19
+ ),
20
+ handleExceptions: true,
21
+ handleRejections: true,
22
+ }),
23
+ ]
24
+ if (process.env.LOGGING_SEQ_SERVERURL) {
25
+ loggerTransports.push(
26
+ new SeqTransport({
27
+ level: process.env.LOGGING_LEVEL,
28
+ serverUrl: process.env.LOGGING_SEQ_SERVERURL,
29
+ apiKey: process.env.LOGGING_SEQ_APIKEY,
30
+ onError: ((e: any) => { console.error('Failed to configure Seq logging transport', e) }),
31
+ format: winston.format.combine(
32
+ winston.format.errors({ stack: true }),
33
+ winston.format.json(),
34
+ ),
35
+ handleExceptions: true,
36
+ handleRejections: true,
37
+ })
38
+ )
39
+ }
40
+
41
+ let logger: winston.Logger | null = null;
42
+
43
+ const createLogger = () => winston.createLogger({
44
+ level: process.env.LOGGING_LEVEL,
45
+ defaultMeta: {
46
+ seekaAppId: process.env.SEEKA_APP_ID,
47
+ Hosting_Provider: 'aws',
48
+ Release_Version: `v${packageJson.version}`,
49
+ Hosting_Region: process.env.AWS_REGION,
50
+ Aws_Xray_TraceId: process.env._X_AMZN_TRACE_ID,
51
+ Aws_Lambda_FunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
52
+ Service: `app/${packageJson.name}`,
53
+ },
54
+ transports: loggerTransports,
55
+ exitOnError: false,
56
+ });
57
+
58
+ export const getLogger = () => {
59
+ if (!logger || logger.closed) {
60
+ // If the AWS lambda host is reused then the logger will be closed and we need to recreate it
61
+ logger = createLogger();
62
+ }
63
+
64
+ return logger;
65
+ };
66
+
67
+ export const childLogger = (meta: object) => getLogger().child(meta);
68
+
69
+ export const webhookLogger = (payload: SeekaWebhookPayload, functionContext: Context) => {
70
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ const meta: any = {
72
+ seekaWebhookType: payload.type,
73
+ seekaWebhookIsTest: payload.isTest,
74
+ RequestId: payload.requestId,
75
+ AwsLambda_RequestId: functionContext.awsRequestId,
76
+ CausationId: payload.causationId,
77
+ CorrelationId: payload.causationId,
78
+ }
79
+
80
+ const context = (payload as SeekaWebhookPayloadOfSeekaAppWebhookContext).context;
81
+ if (context) {
82
+ meta.seekaAppInstallId = context.applicationInstallId;
83
+ meta.seekaAppInstallOrganisationBrandId = context.organisationBrandId;
84
+ meta.seekaAppInstallOrganisationId = context.organisationId;
85
+ }
86
+
87
+ return childLogger(meta)
88
+ }
@@ -0,0 +1,41 @@
1
+ import winston, { Logger } from 'winston';
2
+
3
+ import { connect, disconnect, isConnected } from '../state/redis';
4
+
5
+ export const startServices = async (logger: Logger) => {
6
+ logger.debug(`Trying to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`)
7
+ try {
8
+ if (isConnected()) {
9
+ logger.verbose(`Redis already connected - ${process.env.REDIS_CONNECTION_HOST}`)
10
+ }
11
+ else {
12
+ logger.profile('service.redis.connect')
13
+ await connect();
14
+ logger.profile('service.redis.connect')
15
+ logger.debug(`Redis connected - ${process.env.REDIS_CONNECTION_HOST}`)
16
+ }
17
+ }
18
+ catch (err) {
19
+ logger.error(`Failed to connect to Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
20
+ throw err;
21
+ }
22
+ }
23
+
24
+ export const stopServices = async (logger: Logger) => {
25
+ logger.debug(`Trying to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`)
26
+ try {
27
+ if (isConnected() === false) {
28
+ logger.verbose(`Redis already disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
29
+ }
30
+ else {
31
+ logger.profile('service.redis.disconnect')
32
+ await disconnect();
33
+ logger.profile('service.redis.disconnect')
34
+ logger.info(`Redis disconnected - ${process.env.REDIS_CONNECTION_HOST}`)
35
+ }
36
+ }
37
+ catch (err) {
38
+ logger.error(`Failed to disconnect from Redis - ${process.env.REDIS_CONNECTION_HOST}`, { ex: winston.exceptions.getAllInfo(err) })
39
+ throw err;
40
+ }
41
+ }
@@ -0,0 +1,64 @@
1
+ import { createClient } from 'redis';
2
+
3
+ import { getLogger } from '../../logging';
4
+
5
+ const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
6
+ const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${process.env.REDIS_CONNECTION_PASSWORD}@${process.env.REDIS_CONNECTION_HOST}:${process.env.REDIS_CONNECTION_PORT}`;
7
+
8
+ const redisClient = createClient({
9
+ url: redisConn
10
+ })
11
+ .on('error', err => getLogger().error('Redis Client ', { error: err }));
12
+
13
+ export const connect = async () => {
14
+ await redisClient.connect();
15
+ }
16
+
17
+ export const isConnected = () => {
18
+ return redisClient.isOpen;
19
+ }
20
+
21
+ export const disconnect = async () => {
22
+ await redisClient.disconnect();
23
+ }
24
+
25
+ const getKeyPrefix = (stateType: string) => `seeka:app:${process.env.SEEKA_APP_ID}:${stateType}`
26
+ const getKey = (stateType: string, key: string) => `${getKeyPrefix(stateType)}:${key}`
27
+
28
+ export async function getOrCreate<TState>(stateType: string, key: string, toCreate: TState): Promise<TState> {
29
+ const fullKey = getKey(stateType, key);
30
+ const existingStr = await redisClient.get(fullKey);
31
+ if (existingStr) return JSON.parse(existingStr);
32
+
33
+ await redisClient.set(fullKey, JSON.stringify(toCreate));
34
+
35
+ return toCreate;
36
+ }
37
+
38
+ export async function tryGet<TState>(stateType: string, key: string): Promise<TState | null> {
39
+ const fullKey = getKey(stateType, key);
40
+ const existingStr = await redisClient.get(fullKey);
41
+ if (existingStr) return JSON.parse(existingStr);
42
+
43
+ return null;
44
+ }
45
+
46
+ export async function getList<TState>(stateType: string): Promise<TState[]> {
47
+ const prefix = getKeyPrefix(stateType);
48
+ const allKeys = await redisClient.keys(`${prefix}:*`);
49
+ const listStr = await redisClient.mGet(allKeys);
50
+
51
+ if (listStr) return listStr.filter(e => Boolean(e)).map(e => JSON.parse(e as string));
52
+
53
+ return [];
54
+ }
55
+
56
+ export async function set<TState>(stateType: string, key: string, toCreate: TState): Promise<void> {
57
+ const fullKey = getKey(stateType, key);
58
+ await redisClient.set(fullKey, JSON.stringify(toCreate));
59
+ }
60
+
61
+ export async function remove(stateType: string, key: string): Promise<void> {
62
+ const fullKey = getKey(stateType, key);
63
+ await redisClient.del(fullKey);
64
+ }
@@ -0,0 +1,67 @@
1
+ import type { Logger } from 'winston';
2
+
3
+ import { getList, remove, set, tryGet } from '../../state/redis';
4
+
5
+ export interface SeekaAppInstallState {
6
+ /** ID of the organisation that installed the app */
7
+ organisationId: string;
8
+ /** ID of the brand that installed the app */
9
+ organisationBrandId: string;
10
+ /** ID of the installation of the app */
11
+ applicationInstallId: string;
12
+ // Installation settings provided by the user installing the app
13
+ installationSettings: SampleAppInstallSettings;
14
+ // State relating to the app and installation of the app
15
+ installationState: SampleAppInstallState;
16
+
17
+ // When the app was installed
18
+ installedAt: string; // new Date().toISOString()
19
+ }
20
+
21
+ export interface SampleAppInstallState {
22
+ stateItem1?: string
23
+ stateItem2?: string
24
+ grantedPermissions?: string[]
25
+ }
26
+
27
+ export type SampleAppInstallSettings = { [key: string]: any; } | {
28
+ myAppInstallSetting1: string | number | undefined;
29
+ myAppInstallSetting2: string | number | undefined;
30
+ }
31
+
32
+ const stateType = 'install'
33
+
34
+ export const tryGetInstallation = async (applicationInstallId: string, throwWhenNotFound: boolean, logger: Logger): Promise<SeekaAppInstallState | null> => {
35
+ const installation = await tryGet<SeekaAppInstallState>(stateType, applicationInstallId);
36
+ if (installation == null && throwWhenNotFound) {
37
+ throw new Error(`Seeka installation ${applicationInstallId} not found`);
38
+ }
39
+
40
+ return installation;
41
+ }
42
+
43
+ export const listInstallations = async (logger: Logger): Promise<SeekaAppInstallState[]> => {
44
+ const installations = await getList<SeekaAppInstallState>(stateType);
45
+
46
+ return installations;
47
+ }
48
+
49
+
50
+ export const createOrUpdateInstallation = async (state: SeekaAppInstallState, logger: Logger): Promise<SeekaAppInstallState> => {
51
+ if (!state.installationState) state.installationState = {};
52
+ if (!state.installedAt) state.installedAt = new Date().toISOString();
53
+
54
+ const creating = (await tryGetInstallation(state.applicationInstallId, false, logger)) === null;
55
+
56
+ await set(stateType, state.applicationInstallId, state);
57
+
58
+ logger.info(creating ? 'Created installation state' : 'Updated installation state', { applicationInstallId: state.applicationInstallId, organisationId: state.organisationId, organisationBrandId: state.organisationBrandId });
59
+
60
+ return state;
61
+ }
62
+
63
+ export const deleteInstallation = async (applicationInstallId: string, logger: Logger): Promise<void> => {
64
+ await remove(stateType, applicationInstallId);
65
+
66
+ logger.info('Deleted installation state', { applicationInstallId });
67
+ }
@@ -0,0 +1,170 @@
1
+ import type { Logger } from 'winston';
2
+
3
+ import { Context } from 'aws-lambda';
4
+ import { Request, Response } from 'express';
5
+ import winston from 'winston';
6
+
7
+ import {
8
+ PersonIdentifiers, SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
9
+ SeekaAppInstallSettingsUpdatedWebhookPayload, SeekaAppUninstalledWebhookPayload,
10
+ SeekaIdentityChangedWebhookPayload, SeekaWebhookCallType, SeekaWebhookPayload,
11
+ throwOnInvalidWebhookSignature
12
+ } from '@seeka-labs/sdk-apps-server';
13
+
14
+ import { webhookLogger } from '../lib/logging';
15
+ import { startServices } from '../lib/services';
16
+ import {
17
+ createOrUpdateInstallation, deleteInstallation, SeekaAppInstallState, tryGetInstallation
18
+ } from '../lib/state/seeka/installations';
19
+
20
+ export async function seekaAppWebhook(req: Request, res: Response, context: Context): Promise<void> {
21
+ const bodyStr = req.body as string;
22
+ const body = JSON.parse(bodyStr) as SeekaWebhookPayload;
23
+
24
+ const logger = webhookLogger(body, context);
25
+ logger.profile('http.seeka.webhook.app')
26
+ logger.verbose('Received webhook from Seeka', { body });
27
+
28
+ // Handle probe
29
+ if (body.type === SeekaWebhookCallType.Probe) {
30
+ res.status(204).send();
31
+ return;
32
+ }
33
+
34
+ // Validate webhook
35
+ try {
36
+ throwOnInvalidWebhookSignature(process.env.SEEKA_APP_SECRET as string, req.headers, bodyStr);
37
+ logger.debug('Webhook signature validated', { body });
38
+ }
39
+ catch {
40
+ logger.warn('Webhook signature invalid', { body });
41
+ res.status(401).json({ error: "Webhook call invalid" }).send();
42
+ return;
43
+ }
44
+
45
+ if (body.isTest) {
46
+ // This is a test webhook call
47
+ res.status(204).send();
48
+ return;
49
+ }
50
+
51
+ await startServices(logger);
52
+
53
+ // Check if the webhook is for an app we have installed
54
+ let installation: SeekaAppInstallState | null = null;
55
+ if (body.type != SeekaWebhookCallType.AppInstalled) {
56
+ installation = await tryGetInstallation((body as SeekaAppInstalledWebhookPayload).context?.applicationInstallId as string, false, logger);
57
+ if (installation == null) {
58
+ logger.warn('Webhook call cannot be processed as the installation ID is not known by this app', { body });
59
+
60
+ res.status(422).json({ error: "App not installed" }).send();
61
+ return;
62
+ }
63
+ }
64
+
65
+ // Do something
66
+ try {
67
+ switch (body.type) {
68
+ case SeekaWebhookCallType.AppInstalled:
69
+ {
70
+ await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
71
+ break;
72
+ }
73
+ case SeekaWebhookCallType.AppInstallSettingsUpdated:
74
+ {
75
+ await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
76
+ break;
77
+ }
78
+ case SeekaWebhookCallType.AppUninstalled:
79
+ {
80
+ if (!body.isTest) {
81
+ const payload = body as SeekaAppUninstalledWebhookPayload;
82
+ await deleteInstallation(payload.context?.applicationInstallId as string, logger)
83
+ }
84
+ break;
85
+ }
86
+ case SeekaWebhookCallType.ActivityAccepted:
87
+ {
88
+ const payload = body as SeekaActivityAcceptedWebhookPayload;
89
+ await handleSeekaActivity(payload, logger);
90
+
91
+ break;
92
+ }
93
+ case SeekaWebhookCallType.IdentityChanged:
94
+ {
95
+ const payload = body as SeekaIdentityChangedWebhookPayload;
96
+ break;
97
+ }
98
+ }
99
+
100
+ res.status(204).send();
101
+ }
102
+ catch (err) {
103
+ logger.error('Failed to handle webhook', { ex: winston.exceptions.getAllInfo(err) });
104
+ res.status(500).json({ error: "Request failed" }).send();
105
+ }
106
+ finally {
107
+ logger.profile('http.seeka.webhook.app')
108
+ logger.verbose('Seeka webhook handled');
109
+ }
110
+ }
111
+
112
+ const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger) => {
113
+ if (payload.isTest) return;
114
+
115
+ const installation = await createOrUpdateInstallation({
116
+ ...payload.context,
117
+ installationState: {
118
+ grantedPermissions: payload.content?.grantedPermissions || []
119
+ },
120
+ applicationInstallId: payload.context?.applicationInstallId as string,
121
+ organisationBrandId: payload.context?.organisationBrandId as string,
122
+ organisationId: payload.context?.organisationId as string,
123
+ installedAt: new Date().toISOString(),
124
+ installationSettings: payload.content?.installationSettings || {}
125
+ }, logger)
126
+ }
127
+
128
+ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger) => {
129
+ if (payload.isTest) return;
130
+
131
+ const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
132
+ const installation = await createOrUpdateInstallation({
133
+ ...existingInstallation,
134
+ installationState: {
135
+ ...existingInstallation.installationState,
136
+ grantedPermissions: payload.content?.grantedPermissions || []
137
+ },
138
+ installationSettings: payload.content?.installationSettings || {},
139
+ }, logger)
140
+ }
141
+
142
+ const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
143
+ // const context = activity.context as SeekaAppWebhookContext;
144
+ // const helper = SeekaAppHelper.create(process.env['SEEKA_APP_SECRET'] as string, {
145
+ // organisationId: context.organisationId as string,
146
+ // applicationInstallId: context.applicationInstallId as string,
147
+ // applicationId: process.env['SEEKA_APP_ID'] as string,
148
+ // }, { name, version }, logger);
149
+
150
+ // // Append a first name to the identity
151
+ // await helper.api.mergeIdentity({
152
+ // seekaPId: activity.content?.personId,
153
+ // firstName: [
154
+ // 'firstname_' + new Date().getTime()
155
+ // ]
156
+ // }, {
157
+ // method: 'toremove',
158
+ // origin: TrackingEventSourceOriginType.Server
159
+ // })
160
+
161
+ // // Fire off a tracking event
162
+ // await helper.api.trackActivity({
163
+ // activityName: TrackingActivityNames.Custom,
164
+ // activityNameCustom: 'seeka-app-activity-accepted',
165
+ // activityId: 'act' + new Date().getTime(),
166
+ // }, activity.content?.personId as string, {
167
+ // method: 'toremove',
168
+ // origin: TrackingEventSourceOriginType.Server
169
+ // })
170
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": false,
4
+ "skipLibCheck": true,
5
+ "strict": true,
6
+ "esModuleInterop": true,
7
+ "preserveConstEnums": true,
8
+ "outDir": "dist",
9
+ "rootDir": ".",
10
+ "module": "commonjs",
11
+ "target": "es6",
12
+ "inlineSourceMap": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "typeRoots": [
17
+ "node_modules/@types"
18
+ ],
19
+ "types": [
20
+ "node"
21
+ ]
22
+ },
23
+ "include": [
24
+ "src/index.ts"
25
+ ],
26
+ "exclude": [
27
+ "node_modules",
28
+ "dist",
29
+ "scripts"
30
+ ]
31
+ }
@@ -0,0 +1,10 @@
1
+ /* eslint-env node */
2
+ module.exports = {
3
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
4
+ parser: '@typescript-eslint/parser',
5
+ plugins: ['@typescript-eslint'],
6
+ root: true,
7
+ rules: {
8
+ "@typescript-eslint/no-unused-vars": "warn"
9
+ }
10
+ };
@@ -0,0 +1,48 @@
1
+ bin
2
+ obj
3
+ csx
4
+ .vs
5
+ edge
6
+ Publish
7
+
8
+ *.user
9
+ *.suo
10
+ *.cscfg
11
+ *.Cache
12
+ project.lock.json
13
+
14
+ /packages
15
+ /TestResults
16
+
17
+ /tools/NuGet.exe
18
+ /App_Data
19
+ /secrets
20
+ /data
21
+ .secrets
22
+ appsettings.json
23
+ local.settings.json
24
+
25
+ node_modules
26
+ dist
27
+
28
+ # Local python packages
29
+ .python_packages/
30
+
31
+ # Python Environments
32
+ .env
33
+ .venv
34
+ env/
35
+ venv/
36
+ ENV/
37
+ env.bak/
38
+ venv.bak/
39
+
40
+ # Byte-compiled / optimized / DLL files
41
+ __pycache__/
42
+ *.py[cod]
43
+ *$py.class
44
+
45
+ # Azurite artifacts
46
+ __blobstorage__
47
+ __queuestorage__
48
+ __azurite_db*__.json
@@ -0,0 +1,17 @@
1
+ *.js.map
2
+ *.ts
3
+ .git*
4
+ .vscode
5
+ local.settings.json
6
+ test
7
+ scripts/
8
+ src/
9
+ getting_started.md
10
+ node_modules/@types/
11
+ node_modules/azure-functions-core-tools/
12
+ node_modules/typescript/
13
+ node_modules/@typescript-eslint/eslint-plugin/
14
+ node_modules/@typescript-eslint/parser/
15
+ node_modules/eslint/
16
+ node_modules/ngrok/
17
+ node_modules/rimraf/
@@ -0,0 +1,33 @@
1
+ stages:
2
+ - deploy
3
+
4
+ variables:
5
+ AZURE_FUNC_RESOURCE_NAME: seeka-app-example-name
6
+ CI_DEBUG_TRACE: "true"
7
+
8
+ workflow:
9
+ rules:
10
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
11
+ when: never
12
+ - if: '$CI_COMMIT_BRANCH == "main" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "web")'
13
+ when: always
14
+ - if: '$CI_COMMIT_BRANCH == "staging" && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "web")'
15
+ when: always
16
+
17
+ deploy:
18
+ stage: deploy
19
+ image: node:18-bullseye
20
+ before_script:
21
+ - echo "installing azure cli"
22
+ - curl -sL https://aka.ms/InstallAzureCLIDeb | bash
23
+ - az --version
24
+ - echo "logging in to azure"
25
+ - az login --service-principal --username "$AZURE_SERVICEPRINCIPAL_CLIENTID" --password "$AZURE_SERVICEPRINCIPAL_SECRET" --tenant "$AZURE_TENANT_ID"
26
+ - az account set --subscription "$AZURE_SUBSCRIPTION_ID"
27
+ script:
28
+ - echo "deploying azure func $AZURE_FUNC_RESOURCE_NAME to slot $AZURE_FUNC_SLOT from branch $CI_COMMIT_BRANCH"
29
+ - yarn install --production=false
30
+ - yarn run clean && yarn run build && yarn func azure functionapp publish $AZURE_FUNC_RESOURCE_NAME --no-build --javascript
31
+ only:
32
+ - main
33
+ - staging
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "ms-azuretools.vscode-azurefunctions",
4
+ "ms-vscode.vscode-node-azure-pack",
5
+ "dbaeumer.vscode-eslint"
6
+ ]
7
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Attach to Node Functions",
6
+ "type": "node",
7
+ "request": "attach",
8
+ "port": 9229,
9
+ "preLaunchTask": "func: host start",
10
+ "restart": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "azureFunctions.deploySubpath": ".",
3
+ "azureFunctions.postDeployTask": "yarn install (functions)",
4
+ "azureFunctions.projectLanguage": "TypeScript",
5
+ "azureFunctions.projectRuntime": "~4",
6
+ "debug.internalConsoleOptions": "neverOpen",
7
+ "azureFunctions.projectLanguageModel": 4,
8
+ "azureFunctions.preDeployTask": "yarn prune (functions)"
9
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "type": "func",
6
+ "label": "func: host start",
7
+ "command": "host start --port 7072",
8
+ "problemMatcher": "$func-node-watch",
9
+ "isBackground": true,
10
+ "dependsOn": "yarn build (functions)"
11
+ },
12
+ {
13
+ "type": "shell",
14
+ "label": "yarn build (functions)",
15
+ "command": "yarn run watch",
16
+ "dependsOn": "yarn clean (functions)",
17
+ "problemMatcher": "$tsc-watch",
18
+ "isBackground": true
19
+ },
20
+ {
21
+ "type": "shell",
22
+ "label": "yarn install (functions)",
23
+ "command": "yarn install"
24
+ },
25
+ {
26
+ "type": "shell",
27
+ "label": "yarn prune (functions)",
28
+ "command": "yarn prune --production",
29
+ "dependsOn": "yarn build (functions)",
30
+ "problemMatcher": []
31
+ },
32
+ {
33
+ "type": "shell",
34
+ "label": "yarn clean (functions)",
35
+ "command": "yarn run clean",
36
+ "dependsOn": "yarn install (functions)"
37
+ }
38
+ ]
39
+ }