Skip to content

Commit

Permalink
Merge pull request #13 from Mermaid-Chart/chore/sdk-add-linting
Browse files Browse the repository at this point in the history
build(sdk): Add ESLint/prettier for the `@mermaidchart/sdk` library
  • Loading branch information
aloisklink authored May 14, 2024
2 parents 5e20ece + d536be5 commit 3fd5a09
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 53 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down
1 change: 1 addition & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"version": "0.2",
"language": "en",
"words": [
"Alois",
"Cataa",
"colour",
"Cookiebot",
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
84 changes: 48 additions & 36 deletions packages/sdk/src/index.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,32 @@ 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');

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.`,
);
}

Expand All @@ -44,25 +48,30 @@ 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.`
`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.`
`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');
Expand All @@ -78,17 +87,19 @@ const documentMatcher = expect.objectContaining({
/**
* Cleanup created documents at the end of this test.
*/
const documentsToDelete = new Set<MCDocument["documentID"]>();
afterAll(async() => {
await Promise.all(Array.from(documentsToDelete).map(async(document) => {
if (documentsToDelete.delete(document)) {
await client.deleteDocument(document);
}
}));
const documentsToDelete = new Set<MCDocument['documentID']>();
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);
Expand All @@ -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);

Expand All @@ -115,33 +126,35 @@ describe('setDocument', () => {
await client.setDocument({
documentID: newDocument.documentID,
projectID: newDocument.projectID,
title: "@mermaidchart/sdk E2E test diagram",
title: '@mermaidchart/sdk E2E test diagram',
code,
});

const updatedDoc = await client.getDocument({
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);
Expand All @@ -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);
Expand All @@ -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({
Expand All @@ -190,4 +203,3 @@ describe("getDocument", () => {
expect(error?.response?.status).toBe(404);
});
});

2 changes: 1 addition & 1 deletion packages/sdk/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
20 changes: 11 additions & 9 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,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<void> {
this.axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
Expand All @@ -152,7 +152,7 @@ export class MermaidChart {
this.accessToken = accessToken;
}

public async resetAccessToken(): Promise<void> {
public resetAccessToken(): void {
this.accessToken = undefined;
this.axios.defaults.headers.common['Authorization'] = `Bearer none`;
}
Expand Down Expand Up @@ -204,38 +204,40 @@ export class MermaidChart {
public async getDocument(
document: Pick<MCDocument, 'documentID'> | Pick<MCDocument, 'documentID' | 'major' | 'minor'>,
) {
const {data} = await this.axios.get<MCDocument>(URLS.rest.documents.pick(document).self);
const { data } = await this.axios.get<MCDocument>(URLS.rest.documents.pick(document).self);
return data;
}

/**
* Update the given document.
*
* @param document The document to update.
* @param document - The document to update.
*/
public async setDocument(
document: Pick<MCDocument, 'documentID' | 'projectID'> & Partial<MCDocument>,
) {
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,
);

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)}`,
);
}
}

/**
* 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']) {
const deletedDocument = await this.axios.delete<Document>(
URLS.rest.documents.pick({documentID}).self,
URLS.rest.documents.pick({ documentID }).self,
{}, // force sending empty JSON to avoid triggering CSRF check
);
return deletedDocument.data;
Expand Down
14 changes: 8 additions & 6 deletions packages/sdk/src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export const URLS = {
},
rest: {
documents: {
pick: (opts: Pick<MCDocument, 'documentID'> | Pick<MCDocument, 'documentID' | 'major' | 'minor'>) => {
const {documentID} = opts;
let queryParams = "";
pick: (
opts: Pick<MCDocument, 'documentID'> | Pick<MCDocument, 'documentID' | 'major' | 'minor'>,
) => {
const { documentID } = opts;
let queryParams = '';

if ('major' in opts) {
const {major, minor} = opts;
const { major, minor } = opts;

queryParams = `v${major ?? 0}.${minor ?? 1}`;
}
Expand All @@ -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`,
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3fd5a09

Please sign in to comment.