From 8aa13a44ffef2961605e1f512fa252df690098a7 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Wed, 29 Nov 2023 11:00:53 +0000 Subject: [PATCH 1/5] build(sdk): add `lint`/`lint:fix` scripts --- packages/sdk/package.json | 8 +++++++- pnpm-lock.yaml | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index e87f246..6a751ce 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -14,6 +14,8 @@ "build:code:browser": "esbuild src/index.ts --bundle --minify --outfile=dist/bundle.iife.js", "build:code:node": "esbuild src/index.ts --bundle --platform=node --target=node18.18 --format=esm --packages=external --minify --outfile=dist/index.mjs", "build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly", + "lint": "eslint src/ && prettier --check src/", + "lint:fix": "eslint --fix src/ && prettier --write src/", "prepare": "pnpm run build", "test": "vitest src/", "test:e2e": "vitest --config vitest.config.e2e.ts" @@ -31,7 +33,11 @@ }, "devDependencies": { "@types/node": "^18.18.0", - "@types/uuid": "^9.0.2" + "@types/uuid": "^9.0.2", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "eslint": "^8.54.0", + "prettier": "^3.0.3" }, "publishConfig": { "access": "public" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a051e3..bf17161 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -408,6 +408,18 @@ importers: '@types/uuid': specifier: ^9.0.2 version: 9.0.2 + '@typescript-eslint/eslint-plugin': + specifier: ^6.11.0 + version: 6.12.0(@typescript-eslint/parser@6.13.1)(eslint@8.54.0)(typescript@5.2.2) + '@typescript-eslint/parser': + specifier: ^6.11.0 + version: 6.13.1(eslint@8.54.0)(typescript@5.2.2) + eslint: + specifier: ^8.54.0 + version: 8.54.0 + prettier: + specifier: ^3.0.3 + version: 3.0.3 packages: From 1e5ff67ca41ad7a5ba908760ecb5d1da640a53ac Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Wed, 29 Nov 2023 11:02:37 +0000 Subject: [PATCH 2/5] style(sdk): fix ESLint issues Most of these were autofixed with `eslint --fix src/` --- cSpell.json | 1 + packages/sdk/src/index.e2e.test.ts | 8 ++++---- packages/sdk/src/index.test.ts | 2 +- packages/sdk/src/index.ts | 11 ++++++----- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cSpell.json b/cSpell.json index 3fabbad..c6cb8b9 100644 --- a/cSpell.json +++ b/cSpell.json @@ -2,6 +2,7 @@ "version": "0.2", "language": "en", "words": [ + "Alois", "Cataa", "colour", "Cookiebot", diff --git a/packages/sdk/src/index.e2e.test.ts b/packages/sdk/src/index.e2e.test.ts index 612f83e..76cef91 100644 --- a/packages/sdk/src/index.e2e.test.ts +++ b/packages/sdk/src/index.e2e.test.ts @@ -6,7 +6,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import process from 'node:process'; import { AxiosError } from 'axios'; -import { MCDocument } from './types.js'; +import type { MCDocument } from './types.js'; let testProjectId = '316557b3-cb6f-47ed-acf7-fcfb7ce188d5'; let baseURL = new URL('https://test.mermaidchart.com'); @@ -44,14 +44,14 @@ beforeAll(async() => { // confirm that testProjectId is valid if (process.env.TEST_MERMAIDCHART_PROJECT_ID) { testProjectId = process.env.TEST_MERMAIDCHART_PROJECT_ID; - if (!projects.find((project) => project.id === testProjectId)) { + if (!projects.some((project) => project.id === testProjectId)) { throw new Error( `Invalid environment variable TEST_MERMAIDCHART_PROJECT_ID. ` + `Please go to ${new URL('/app/projects', baseURL)} and create one that you have access to.` ); } } else { - if (!projects.find((project) => project.id === testProjectId)) { + if (!projects.some((project) => project.id === testProjectId)) { throw new Error( `Missing environment variable TEST_MERMAIDCHART_PROJECT_ID. ` + `Please go to ${new URL('/app/projects', baseURL)} and create one.` @@ -80,7 +80,7 @@ const documentMatcher = expect.objectContaining({ */ const documentsToDelete = new Set(); afterAll(async() => { - await Promise.all(Array.from(documentsToDelete).map(async(document) => { + await Promise.all([...documentsToDelete].map(async(document) => { if (documentsToDelete.delete(document)) { await client.deleteDocument(document); } diff --git a/packages/sdk/src/index.test.ts b/packages/sdk/src/index.test.ts index 2f39cbb..0030f1d 100644 --- a/packages/sdk/src/index.test.ts +++ b/packages/sdk/src/index.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { MermaidChart } from './index.js'; -import { AuthorizationData } from './types.js'; +import type { AuthorizationData } from './types.js'; import { OAuth2Client } from '@badgateway/oauth2-client'; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index cb27da1..86dbda9 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -57,7 +57,8 @@ export class MermaidChart { this.axios.interceptors.response.use((res: AxiosResponse) => { // Reset token if a 401 is thrown if (res.status === 401) { - this.resetAccessToken(); + // don't care if this function rejects/resolves + void this.resetAccessToken(); } return res; }); @@ -143,7 +144,7 @@ export class MermaidChart { /** * This method is used after authentication to save the access token. * It should be called by the plugins if any update to access token is made outside this lib. - * @param accessToken access token to use for requests + * @param accessToken - access token to use for requests */ public async setAccessToken(accessToken: string): Promise { this.axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`; @@ -211,12 +212,12 @@ export class MermaidChart { /** * Update the given document. * - * @param document The document to update. + * @param document - The document to update. */ public async setDocument( document: Pick & Partial, ) { - const {data} = await this.axios.put<{result: "ok"} | {result: "failed", error: any}>( + const {data} = await this.axios.put<{result: "ok"} | {result: "failed", error: unknown}>( URLS.rest.documents.pick(document).self, document, ); @@ -230,7 +231,7 @@ export class MermaidChart { /** * Delete the given document. - * @param documentID The ID of the document to delete. + * @param documentID - The ID of the document to delete. * @returns Metadata about the deleted document. */ public async deleteDocument(documentID: MCDocument['documentID']) { From 7784d35f548ef0cbfec0fc7349d2475c3b5afb00 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Wed, 29 Nov 2023 11:10:19 +0000 Subject: [PATCH 3/5] style(sdk): format with `prettier --write src/` --- packages/sdk/src/index.e2e.test.ts | 78 +++++++++++++++++------------- packages/sdk/src/index.ts | 12 +++-- packages/sdk/src/urls.ts | 14 +++--- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/packages/sdk/src/index.e2e.test.ts b/packages/sdk/src/index.e2e.test.ts index 76cef91..a269881 100644 --- a/packages/sdk/src/index.e2e.test.ts +++ b/packages/sdk/src/index.e2e.test.ts @@ -13,21 +13,25 @@ let baseURL = new URL('https://test.mermaidchart.com'); let client: MermaidChart; -beforeAll(async() => { +beforeAll(async () => { if (process.env.TEST_MERMAIDCHART_BASE_URL) { try { baseURL = new URL(process.env.TEST_MERMAIDCHART_BASE_URL); } catch (err) { - throw new Error("Invalid URL in environment variable TEST_MERMAIDCHART_BASE_URL", { cause: err}); + throw new Error('Invalid URL in environment variable TEST_MERMAIDCHART_BASE_URL', { + cause: err, + }); } } else { - process.emitWarning(`Missing environment variable TEST_MERMAIDCHART_BASE_URL. Defaulting to ${baseURL.href}.`); + process.emitWarning( + `Missing environment variable TEST_MERMAIDCHART_BASE_URL. Defaulting to ${baseURL.href}.`, + ); } if (!process.env.TEST_MERMAIDCHART_API_TOKEN) { throw new Error( - "Missing required environment variable TEST_MERMAIDCHART_API_TOKEN. " - + `Please go to ${new URL('/app/user/settings', baseURL)} and create one.` + 'Missing required environment variable TEST_MERMAIDCHART_API_TOKEN. ' + + `Please go to ${new URL('/app/user/settings', baseURL)} and create one.`, ); } @@ -46,23 +50,28 @@ beforeAll(async() => { testProjectId = process.env.TEST_MERMAIDCHART_PROJECT_ID; if (!projects.some((project) => project.id === testProjectId)) { throw new Error( - `Invalid environment variable TEST_MERMAIDCHART_PROJECT_ID. ` - + `Please go to ${new URL('/app/projects', baseURL)} and create one that you have access to.` + `Invalid environment variable TEST_MERMAIDCHART_PROJECT_ID. ` + + `Please go to ${new URL( + '/app/projects', + baseURL, + )} and create one that you have access to.`, ); } } else { if (!projects.some((project) => project.id === testProjectId)) { throw new Error( - `Missing environment variable TEST_MERMAIDCHART_PROJECT_ID. ` - + `Please go to ${new URL('/app/projects', baseURL)} and create one.` + `Missing environment variable TEST_MERMAIDCHART_PROJECT_ID. ` + + `Please go to ${new URL('/app/projects', baseURL)} and create one.`, ); } - process.emitWarning(`Missing optional environment variable TEST_MERMAIDCHART_PROJECT_ID. Defaulting to ${testProjectId}`); + process.emitWarning( + `Missing optional environment variable TEST_MERMAIDCHART_PROJECT_ID. Defaulting to ${testProjectId}`, + ); } }); describe('getUser', () => { - it("should get user", async() => { + it('should get user', async () => { const user = await client.getUser(); expect(user).toHaveProperty('emailAddress'); @@ -78,17 +87,19 @@ const documentMatcher = expect.objectContaining({ /** * Cleanup created documents at the end of this test. */ -const documentsToDelete = new Set(); -afterAll(async() => { - await Promise.all([...documentsToDelete].map(async(document) => { - if (documentsToDelete.delete(document)) { - await client.deleteDocument(document); - } - })); +const documentsToDelete = new Set(); +afterAll(async () => { + await Promise.all( + [...documentsToDelete].map(async (document) => { + if (documentsToDelete.delete(document)) { + await client.deleteDocument(document); + } + }), + ); }); describe('createDocument', () => { - it('should create document in project', async() => { + it('should create document in project', async () => { const existingDocuments = await client.getDocuments(testProjectId); const newDocument = await client.createDocument(testProjectId); @@ -105,7 +116,7 @@ describe('createDocument', () => { }); describe('setDocument', () => { - it('should set document', async() => { + it('should set document', async () => { const newDocument = await client.createDocument(testProjectId); documentsToDelete.add(newDocument.documentID); @@ -115,7 +126,7 @@ describe('setDocument', () => { await client.setDocument({ documentID: newDocument.documentID, projectID: newDocument.projectID, - title: "@mermaidchart/sdk E2E test diagram", + title: '@mermaidchart/sdk E2E test diagram', code, }); @@ -123,25 +134,27 @@ describe('setDocument', () => { documentID: newDocument.documentID, }); expect(updatedDoc).toMatchObject({ - title: "@mermaidchart/sdk E2E test diagram", + title: '@mermaidchart/sdk E2E test diagram', code, }); }); - it('should throw an error on invalid data', async() => { + it('should throw an error on invalid data', async () => { const newDocument = await client.createDocument(testProjectId); documentsToDelete.add(newDocument.documentID); - await expect(client.setDocument({ - documentID: newDocument.documentID, - // @ts-expect-error not setting diagram `projectID` should throw an error - projectID: null, - })).rejects.toThrowError("400"); // should throw HTTP 400 error + await expect( + client.setDocument({ + documentID: newDocument.documentID, + // @ts-expect-error not setting diagram `projectID` should throw an error + projectID: null, + }), + ).rejects.toThrowError('400'); // should throw HTTP 400 error }); }); describe('deleteDocument', () => { - it('should delete document', async() => { + it('should delete document', async () => { const newDocument = await client.createDocument(testProjectId); expect(await client.getDocuments(testProjectId)).toContainEqual(newDocument); @@ -154,8 +167,8 @@ describe('deleteDocument', () => { }); }); -describe("getDocument", () => { - it("should get diagram", async() => { +describe('getDocument', () => { + it('should get diagram', async () => { const newDocument = await client.createDocument(testProjectId); documentsToDelete.add(newDocument.documentID); @@ -176,7 +189,7 @@ describe("getDocument", () => { expect(earliestDocument).toStrictEqual(documentMatcher); }); - it("should throw 404 on unknown document", async() => { + it('should throw 404 on unknown document', async () => { let error: AxiosError | undefined = undefined; try { await client.getDocument({ @@ -190,4 +203,3 @@ describe("getDocument", () => { expect(error?.response?.status).toBe(404); }); }); - diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 86dbda9..eb9df4f 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -205,7 +205,7 @@ export class MermaidChart { public async getDocument( document: Pick | Pick, ) { - const {data} = await this.axios.get(URLS.rest.documents.pick(document).self); + const { data } = await this.axios.get(URLS.rest.documents.pick(document).self); return data; } @@ -217,14 +217,16 @@ export class MermaidChart { public async setDocument( document: Pick & Partial, ) { - const {data} = await this.axios.put<{result: "ok"} | {result: "failed", error: unknown}>( + const { data } = await this.axios.put<{ result: 'ok' } | { result: 'failed'; error: unknown }>( URLS.rest.documents.pick(document).self, document, ); - if (data.result === "failed") { + if (data.result === 'failed') { throw new Error( - `setDocument(${JSON.stringify({documentID: document.documentID})} failed due to ${JSON.stringify(data.error)}` + `setDocument(${JSON.stringify({ + documentID: document.documentID, + })} failed due to ${JSON.stringify(data.error)}`, ); } } @@ -236,7 +238,7 @@ export class MermaidChart { */ public async deleteDocument(documentID: MCDocument['documentID']) { const deletedDocument = await this.axios.delete( - URLS.rest.documents.pick({documentID}).self, + URLS.rest.documents.pick({ documentID }).self, {}, // force sending empty JSON to avoid triggering CSRF check ); return deletedDocument.data; diff --git a/packages/sdk/src/urls.ts b/packages/sdk/src/urls.ts index 3029c7a..c337f0a 100644 --- a/packages/sdk/src/urls.ts +++ b/packages/sdk/src/urls.ts @@ -7,12 +7,14 @@ export const URLS = { }, rest: { documents: { - pick: (opts: Pick | Pick) => { - const {documentID} = opts; - let queryParams = ""; + pick: ( + opts: Pick | Pick, + ) => { + const { documentID } = opts; + let queryParams = ''; if ('major' in opts) { - const {major, minor} = opts; + const { major, minor } = opts; queryParams = `v${major ?? 0}.${minor ?? 1}`; } @@ -21,9 +23,9 @@ export const URLS = { return { presentations: `${baseURL}/presentations`, self: baseURL, - withVersion: `${baseURL}${queryParams}` + withVersion: `${baseURL}${queryParams}`, }; - } + }, }, users: { self: `/rest-api/users/me`, From 24055b2cc3a01b9277aa33f7bc7f198678c93b0f Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Wed, 29 Nov 2023 13:02:27 +0000 Subject: [PATCH 4/5] chore(lint): allow unused vars starting with `_` This matches TypeScript's default rules (e.g. what VS Code uses). --- .eslintrc.cjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 89a5d44..1f85637 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -48,6 +48,16 @@ module.exports = { '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + // TypeScript by default allows params starting with _ + varsIgnorePattern: /^_/.source, + argsIgnorePattern: /^_/.source, + caughtErrorsIgnorePattern: /^_/.source, + destructuredArrayIgnorePattern: /^_/.source, + }, + ], '@typescript-eslint/ban-ts-comment': [ 'error', { From d536be5f66c00a09e40caa56f9c37164ca0cff5c Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Mon, 4 Dec 2023 09:02:47 +0000 Subject: [PATCH 5/5] refactor(sdk)!: make `resetAccessToken()` sync Change `MermaidChart#resetAccessToken()` so that it no longer returns a `Promise`. --- packages/sdk/CHANGELOG.md | 4 ++++ packages/sdk/src/index.ts | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index f43ad74..8063089 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changes + +- `MermaidChart#resetAccessToken()` no longer returns a `Promise`. + ## [0.2.0] - 2024-04-11 ### Added diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index eb9df4f..ca212ea 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -57,8 +57,7 @@ export class MermaidChart { this.axios.interceptors.response.use((res: AxiosResponse) => { // Reset token if a 401 is thrown if (res.status === 401) { - // don't care if this function rejects/resolves - void this.resetAccessToken(); + this.resetAccessToken(); } return res; }); @@ -153,7 +152,7 @@ export class MermaidChart { this.accessToken = accessToken; } - public async resetAccessToken(): Promise { + public resetAccessToken(): void { this.accessToken = undefined; this.axios.defaults.headers.common['Authorization'] = `Bearer none`; }