From 18bdeff68b61ddf41615154b2b9a547116107530 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Mon, 29 Aug 2022 12:56:08 +0200 Subject: [PATCH 01/12] Add localEnv apl --- package.json | 4 +- pnpm-lock.yaml | 26 +++++-- src/APL/environmentVariablesAPL.ts | 121 +++++++++++++++++++++++++++++ src/APL/index.ts | 1 + 4 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 src/APL/environmentVariablesAPL.ts diff --git a/package.json b/package.json index a16d6fc..5383eb2 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,13 @@ "fast-glob": "^3.2.11", "graphql": "^16.5.0", "jose": "^4.8.3", + "node-fetch": "^3.2.10", "retes": "^0.32.0", "uuid": "^8.3.2" }, "devDependencies": { "release-it": "^15.4.1", + "@types/node-fetch": "^2.6.2", "@testing-library/dom": "^8.17.1", "@types/debug": "^4.1.7", "@types/node": "^18.6.5", @@ -102,4 +104,4 @@ "url": "https://github.com/saleor/saleor-app-sdk/issues" }, "homepage": "https://github.com/saleor/saleor-app-sdk#readme" -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c905d8..4f8666b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,7 @@ specifiers: '@testing-library/dom': ^8.17.1 '@types/debug': ^4.1.7 '@types/node': ^18.6.5 + '@types/node-fetch': ^2.6.2 '@types/uuid': ^8.3.4 '@typescript-eslint/eslint-plugin': ^5.33.0 '@typescript-eslint/parser': ^5.33.0 @@ -24,6 +25,7 @@ specifiers: husky: ^8.0.1 jose: ^4.8.3 jsdom: ^20.0.0 + node-fetch: ^3.2.10 prettier: 2.7.1 release-it: ^15.4.1 retes: ^0.32.0 @@ -40,6 +42,7 @@ dependencies: fast-glob: 3.2.11 graphql: 16.5.0 jose: 4.8.3 + node-fetch: 3.2.10 retes: 0.32.0 uuid: 8.3.2 @@ -47,6 +50,7 @@ devDependencies: '@testing-library/dom': 8.17.1 '@types/debug': 4.1.7 '@types/node': 18.7.1 + '@types/node-fetch': 2.6.2 '@types/uuid': 8.3.4 '@typescript-eslint/eslint-plugin': 5.33.0_njno5y7ry2l2lcmiu4tywxkwnq '@typescript-eslint/parser': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq @@ -686,6 +690,13 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: true + /@types/node-fetch/2.6.2: + resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} + dependencies: + '@types/node': 18.7.1 + form-data: 3.0.1 + dev: true + /@types/node/18.7.1: resolution: {integrity: sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ==} dev: true @@ -1446,7 +1457,6 @@ packages: /data-uri-to-buffer/4.0.0: resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} engines: {node: '>= 12'} - dev: true /data-urls/3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} @@ -2358,7 +2368,6 @@ packages: dependencies: node-domexception: 1.0.0 web-streams-polyfill: 3.2.1 - dev: true /figures/4.0.1: resolution: {integrity: sha512-rElJwkA/xS04Vfg+CaZodpso7VqBknOYbzi6I76hI4X80RUjkSxO2oAyPmGbuXUppywjqndOrQDl817hDnI++w==} @@ -2418,6 +2427,15 @@ packages: engines: {node: '>= 14.17'} dev: true + /form-data/3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + /form-data/4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2432,7 +2450,6 @@ packages: engines: {node: '>=12.20.0'} dependencies: fetch-blob: 3.2.0 - dev: true /fs-extra/8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} @@ -3494,7 +3511,6 @@ packages: /node-domexception/1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} - dev: true /node-fetch/2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} @@ -3515,7 +3531,6 @@ packages: data-uri-to-buffer: 4.0.0 fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - dev: true /node-releases/2.0.6: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} @@ -4899,7 +4914,6 @@ packages: /web-streams-polyfill/3.2.1: resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} engines: {node: '>= 8'} - dev: true /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} diff --git a/src/APL/environmentVariablesAPL.ts b/src/APL/environmentVariablesAPL.ts new file mode 100644 index 0000000..73119aa --- /dev/null +++ b/src/APL/environmentVariablesAPL.ts @@ -0,0 +1,121 @@ +import { promises as fsPromises } from "fs"; +import fetch from "node-fetch"; + +import { APL, AuthData } from "./apl"; + +interface IEnvVar { + key: string; + value: string; +} + +const ENV_FILE_NAME = ".envfile"; + +const saveDataToFile = async (variables: IEnvVar[]) => { + let currentEnvVars; + try { + await fsPromises.access(ENV_FILE_NAME); + currentEnvVars = JSON.parse(await fsPromises.readFile(ENV_FILE_NAME, "utf-8")); + } catch { + currentEnvVars = {}; + } + + await fsPromises.writeFile( + ENV_FILE_NAME, + JSON.stringify({ + ...currentEnvVars, + ...variables.reduce((acc, cur) => ({ ...acc, [cur.key]: cur.value }), {}), + }) + ); +}; + +const loadDataFromFile = async () => { + try { + await fsPromises.access(ENV_FILE_NAME); + return JSON.parse(await fsPromises.readFile(ENV_FILE_NAME, "utf-8")); + } catch { + return {}; + } +}; + +const saveDataToVercel = async (variables: IEnvVar[]) => { + await fetch(process.env.SALEOR_REGISTER_APP_URL as string, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + token: process.env.SALEOR_DEPLOYMENT_TOKEN, + envs: variables.map(({ key, value }) => ({ key, value })), + }), + }); +}; + +const loadDataFromVercel = () => process.env; + +export const getEnvVars = async () => { + if (process.env.VERCEL === "1") { + return loadDataFromVercel(); + } + return loadDataFromFile(); +}; + +export const setEnvVars = async (variables: IEnvVar[]) => { + console.debug("Setting environment variables: ", variables); + + if (process.env.VERCEL === "1") { + await saveDataToVercel(variables); + } else { + await saveDataToFile(variables); + } +}; + +export const environmentVariablesAPL: APL = { + get: async (domain) => { + const env = await getEnvVars(); + if (domain !== env.SALEOR_DOMAIN || !env.SALEOR_AUTH_TOKEN) { + return undefined; + } + return { + token: env.SALEOR_AUTH_TOKEN, + domain: env.SALEOR_DOMAIN, + }; + }, + set: async (authData: AuthData) => { + await setEnvVars([ + { + key: "SALEOR_AUTH_TOKEN", + value: authData.token, + }, + { + key: "SALEOR_DOMAIN", + value: authData.domain, + }, + ]); + }, + delete: async (domain: string) => { + const env = await getEnvVars(); + + if (domain !== env.SALEOR_DOMAIN) { + return; + } + await setEnvVars([ + { + key: "SALEOR_AUTH_TOKEN", + value: "", + }, + { + key: "SALEOR_DOMAIN", + value: "", + }, + ]); + }, + getAll: async () => { + const env = await getEnvVars(); + if (!env.SALEOR_DOMAIN || !env.SALEOR_AUTH_TOKEN) { + return []; + } + const authData = { + token: env.SALEOR_AUTH_TOKEN, + domain: env.SALEOR_DOMAIN, + }; + return [authData]; + }, +}; diff --git a/src/APL/index.ts b/src/APL/index.ts index 3831bbc..43122ba 100644 --- a/src/APL/index.ts +++ b/src/APL/index.ts @@ -1 +1,2 @@ export * from "./apl"; +export * from "./environmentVariablesAPL"; From b41f54ad4c79920e58d9d1bf370a167bd895cc7f Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Mon, 29 Aug 2022 19:00:02 +0200 Subject: [PATCH 02/12] Split APL into single purpose ones, fix module export issue --- .eslintrc | 2 +- .prettierignore | 1 + package.json | 2 +- src/APL/environmentVariablesAPL.ts | 121 ------------------- src/APL/fileAPL.test.ts | 184 +++++++++++++++++++++++++++++ src/APL/fileAPL.ts | 87 ++++++++++++++ src/APL/index.ts | 3 +- src/APL/vercelAPL.test.ts | 145 +++++++++++++++++++++++ src/APL/vercelAPL.ts | 116 ++++++++++++++++++ 9 files changed, 537 insertions(+), 124 deletions(-) delete mode 100644 src/APL/environmentVariablesAPL.ts create mode 100644 src/APL/fileAPL.test.ts create mode 100644 src/APL/fileAPL.ts create mode 100644 src/APL/vercelAPL.test.ts create mode 100644 src/APL/vercelAPL.ts diff --git a/.eslintrc b/.eslintrc index aa6d44a..20e535a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,7 +9,7 @@ "plugin:@typescript-eslint/recommended", "prettier" // prettier *has* to be the last one, to avoid conflicting rules ], - "ignorePatterns": ["pnpm-lock.yaml"], + "ignorePatterns": ["pnpm-lock.yaml", "dist"], "plugins": ["simple-import-sort", "@typescript-eslint"], "rules": { "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], diff --git a/.prettierignore b/.prettierignore index d605ec8..c50265d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ saleor/api.tsx pnpm-lock.yaml graphql.schema.json lib/$path.ts +dist \ No newline at end of file diff --git a/package.json b/package.json index 5383eb2..dc72a86 100644 --- a/package.json +++ b/package.json @@ -104,4 +104,4 @@ "url": "https://github.com/saleor/saleor-app-sdk/issues" }, "homepage": "https://github.com/saleor/saleor-app-sdk#readme" -} \ No newline at end of file +} diff --git a/src/APL/environmentVariablesAPL.ts b/src/APL/environmentVariablesAPL.ts deleted file mode 100644 index 73119aa..0000000 --- a/src/APL/environmentVariablesAPL.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { promises as fsPromises } from "fs"; -import fetch from "node-fetch"; - -import { APL, AuthData } from "./apl"; - -interface IEnvVar { - key: string; - value: string; -} - -const ENV_FILE_NAME = ".envfile"; - -const saveDataToFile = async (variables: IEnvVar[]) => { - let currentEnvVars; - try { - await fsPromises.access(ENV_FILE_NAME); - currentEnvVars = JSON.parse(await fsPromises.readFile(ENV_FILE_NAME, "utf-8")); - } catch { - currentEnvVars = {}; - } - - await fsPromises.writeFile( - ENV_FILE_NAME, - JSON.stringify({ - ...currentEnvVars, - ...variables.reduce((acc, cur) => ({ ...acc, [cur.key]: cur.value }), {}), - }) - ); -}; - -const loadDataFromFile = async () => { - try { - await fsPromises.access(ENV_FILE_NAME); - return JSON.parse(await fsPromises.readFile(ENV_FILE_NAME, "utf-8")); - } catch { - return {}; - } -}; - -const saveDataToVercel = async (variables: IEnvVar[]) => { - await fetch(process.env.SALEOR_REGISTER_APP_URL as string, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - token: process.env.SALEOR_DEPLOYMENT_TOKEN, - envs: variables.map(({ key, value }) => ({ key, value })), - }), - }); -}; - -const loadDataFromVercel = () => process.env; - -export const getEnvVars = async () => { - if (process.env.VERCEL === "1") { - return loadDataFromVercel(); - } - return loadDataFromFile(); -}; - -export const setEnvVars = async (variables: IEnvVar[]) => { - console.debug("Setting environment variables: ", variables); - - if (process.env.VERCEL === "1") { - await saveDataToVercel(variables); - } else { - await saveDataToFile(variables); - } -}; - -export const environmentVariablesAPL: APL = { - get: async (domain) => { - const env = await getEnvVars(); - if (domain !== env.SALEOR_DOMAIN || !env.SALEOR_AUTH_TOKEN) { - return undefined; - } - return { - token: env.SALEOR_AUTH_TOKEN, - domain: env.SALEOR_DOMAIN, - }; - }, - set: async (authData: AuthData) => { - await setEnvVars([ - { - key: "SALEOR_AUTH_TOKEN", - value: authData.token, - }, - { - key: "SALEOR_DOMAIN", - value: authData.domain, - }, - ]); - }, - delete: async (domain: string) => { - const env = await getEnvVars(); - - if (domain !== env.SALEOR_DOMAIN) { - return; - } - await setEnvVars([ - { - key: "SALEOR_AUTH_TOKEN", - value: "", - }, - { - key: "SALEOR_DOMAIN", - value: "", - }, - ]); - }, - getAll: async () => { - const env = await getEnvVars(); - if (!env.SALEOR_DOMAIN || !env.SALEOR_AUTH_TOKEN) { - return []; - } - const authData = { - token: env.SALEOR_AUTH_TOKEN, - domain: env.SALEOR_DOMAIN, - }; - return [authData]; - }, -}; diff --git a/src/APL/fileAPL.test.ts b/src/APL/fileAPL.test.ts new file mode 100644 index 0000000..f4a56c1 --- /dev/null +++ b/src/APL/fileAPL.test.ts @@ -0,0 +1,184 @@ +import { promises as fsPromises } from "fs"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { FileAPL, loadDataFromFile, saveDataToFile } from "./fileAPL"; + +describe("APL", () => { + describe("FileAPL", () => { + describe("get", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("Returns auth data for existing domain", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + + const apl = new FileAPL(); + + expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData); + }); + + it("Returns undefined for unknown domain", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + + const apl = new FileAPL(); + + expect(await apl.get("unknown-domain.example.com")).toBe(undefined); + }); + }); + + describe("delete", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("Should override file when called with known domain", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + + const apl = new FileAPL(); + + expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData); + }); + + it("Should not delete data when called with unknown domain", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); + + const apl = new FileAPL(); + + await apl.delete("unknown-domain.example.com"); + + expect(spyWriteFile).toBeCalledTimes(0); + }); + }); + + describe("getAll", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("Should return list with one item when auth data are existing", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + + const apl = new FileAPL(); + + expect(await apl.getAll()).toStrictEqual([stubAuthData]); + }); + + it("Should return empty list when auth data are empty", async () => { + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue("{}"); + + const apl = new FileAPL(); + + expect(await apl.getAll()).toStrictEqual([]); + }); + }); + }); + + describe("FileAPL utils", () => { + describe("saveDataToFile", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("Save existing auth data to file", async () => { + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); + await saveDataToFile("test.json", { domain: "example.com", token: "example-token" }); + + expect(spyWriteFile).toBeCalledWith( + "test.json", + JSON.stringify({ + domain: "example.com", + token: "example-token", + }) + ); + }); + + it("Save empty file when no auth data provided", async () => { + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); + await saveDataToFile("test.json"); + expect(spyWriteFile).toBeCalledWith("test.json", "{}"); + }); + + it("Handle write file errors", async () => { + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockImplementation(() => { + throw Error("Write error"); + }); + await saveDataToFile("test.json"); + expect(spyWriteFile).toBeCalledWith("test.json", "{}"); + }); + }); + + describe("loadDataFromFile", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("Load existing auth data", async () => { + vi.spyOn(fsPromises, "access").mockResolvedValue(); + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + expect(await loadDataFromFile("test.json")).toStrictEqual(stubAuthData); + }); + + it("Should return undefined when JSON parse fails", async () => { + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue("Not a valid JSON"); + expect(await loadDataFromFile("test.json")).toBe(undefined); + }); + + it("Should return undefined when auth token is missing", async () => { + vi.spyOn(fsPromises, "access").mockResolvedValue(); + const stubAuthData = { + domain: "example.com", + }; + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + expect(await loadDataFromFile("test.json")).toBe(undefined); + }); + + it("Should return undefined when domain is missing", async () => { + vi.spyOn(fsPromises, "access").mockResolvedValue(); + const stubAuthData = { + token: "token", + }; + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + expect(await loadDataFromFile("test.json")).toBe(undefined); + }); + }); + }); +}); diff --git a/src/APL/fileAPL.ts b/src/APL/fileAPL.ts new file mode 100644 index 0000000..d9d162f --- /dev/null +++ b/src/APL/fileAPL.ts @@ -0,0 +1,87 @@ +import debugPkg from "debug"; +import { promises as fsPromises } from "fs"; + +import { APL, AuthData } from "./apl"; + +const debug = debugPkg.debug("FileAPL"); + +export const loadDataFromFile = async (fileName: string): Promise => { + debug(`Load auth data from the ${fileName} file`); + let parsedData: Record = {}; + try { + await fsPromises.access(fileName); + parsedData = JSON.parse(await fsPromises.readFile(fileName, "utf-8")); + } catch (err) { + debug(`Could not read auth data from the ${fileName} file`, err); + return undefined; + } + const { token, domain } = parsedData; + if (token && domain) { + return { token, domain }; + } + return undefined; +}; + +export const saveDataToFile = async (fileName: string, authData?: AuthData) => { + debug(`Save auth data to the ${fileName} file`); + const newData = authData ? JSON.stringify(authData) : "{}"; + try { + await fsPromises.writeFile(fileName, newData); + } catch (err) { + debug(`Could not save auth data to the ${fileName} file`, err); + } +}; + +export type FileAPLConfig = { + fileName?: string; +}; + +/** + * File APL + * + * The APL store auth data in the json file. + * + * Before using this APL, please take in consideration: + * - only stores single auth data entry (setting up a new one will overwrite previous values) + * - it's not recommended for production use - redeployment of the application will override + * existing values, or data persistence will not be guaranteed at all depending on chosen + * hosting solution + * + */ +export class FileAPL implements APL { + private fileName: string; + + constructor(config: FileAPLConfig = {}) { + this.fileName = config?.fileName || ".auth-data.json"; + } + + async get(domain: string) { + const authData = await loadDataFromFile(this.fileName); + if (domain === authData?.domain) { + return authData; + } + return undefined; + } + + async set(authData: AuthData) { + await saveDataToFile(this.fileName, authData); + } + + async delete(domain: string) { + const authData = await loadDataFromFile(this.fileName); + + if (domain === authData?.domain) { + await saveDataToFile(this.fileName); + } + } + + async getAll() { + const authData = await loadDataFromFile(this.fileName); + + if (!authData) { + return []; + } + + return [authData]; + } +} diff --git a/src/APL/index.ts b/src/APL/index.ts index 43122ba..6d54627 100644 --- a/src/APL/index.ts +++ b/src/APL/index.ts @@ -1,2 +1,3 @@ export * from "./apl"; -export * from "./environmentVariablesAPL"; +export * from "./fileAPL"; +export * from "./vercelAPL"; diff --git a/src/APL/vercelAPL.test.ts b/src/APL/vercelAPL.test.ts new file mode 100644 index 0000000..b4b3386 --- /dev/null +++ b/src/APL/vercelAPL.test.ts @@ -0,0 +1,145 @@ +import { afterEach, describe, expect, it } from "vitest"; + +import { + DOMAIN_VARIABLE_NAME, + SALEOR_DEPLOYMENT_TOKEN, + SALEOR_REGISTER_APP_URL, + TOKEN_VARIABLE_NAME, + VercelAPL, +} from "./vercelAPL"; + +describe("APL", () => { + describe("VercelAPL", () => { + describe("constructor", () => { + const initialEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...initialEnv }; + }); + + it("Raise an error when configuration is missing", async () => { + delete process.env[SALEOR_REGISTER_APP_URL]; + process.env[SALEOR_DEPLOYMENT_TOKEN] = "token"; + + expect(() => new VercelAPL()).toThrow(); + + process.env[SALEOR_REGISTER_APP_URL] = "http://example.com"; + delete process.env[SALEOR_DEPLOYMENT_TOKEN]; + + expect(() => new VercelAPL()).toThrow(); + }); + }); + + it("Constructor with config values", async () => { + expect( + () => + new VercelAPL({ + deploymentToken: "token", + registerAppURL: "http://example.com", + }) + ).not.toThrow(); + }); + + it("Constructor with config values from environment variables", async () => { + process.env[SALEOR_REGISTER_APP_URL] = "http://example.com"; + process.env[SALEOR_DEPLOYMENT_TOKEN] = "token"; + + expect(() => new VercelAPL()).not.toThrow(); + }); + + describe("get", () => { + describe("Read existing auth data from env", () => { + const initialEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...initialEnv }; + }); + + it("Read existing auth data", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; + process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; + + const apl = new VercelAPL({ + deploymentToken: "token", + registerAppURL: "http://example.com", + }); + + expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData); + }); + + it("Return undefined when unknown domain requested", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; + process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; + + const apl = new VercelAPL({ + deploymentToken: "token", + registerAppURL: "http://example.com", + }); + + expect(await apl.get("unknown-domain.example.com")).toBe(undefined); + }); + + it("Return undefined when no data is defined", async () => { + process.env[TOKEN_VARIABLE_NAME] = undefined; + process.env[DOMAIN_VARIABLE_NAME] = undefined; + + const apl = new VercelAPL({ + deploymentToken: "token", + registerAppURL: "http://example.com", + }); + + expect(await apl.get("example.com")).toBe(undefined); + }); + }); + }); + + describe("getAll", () => { + describe("Read existing auth data from env", () => { + const initialEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...initialEnv }; + }); + + it("Read existing auth data", async () => { + const stubAuthData = { + domain: "example.com", + token: "example-token", + }; + + process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; + process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; + + const apl = new VercelAPL({ + deploymentToken: "token", + registerAppURL: "http://example.com", + }); + + expect(await apl.getAll()).toStrictEqual([stubAuthData]); + }); + + it("Return empty list when no auth data are existing", async () => { + process.env[TOKEN_VARIABLE_NAME] = undefined; + process.env[DOMAIN_VARIABLE_NAME] = undefined; + + const apl = new VercelAPL({ + deploymentToken: "token", + registerAppURL: "http://example.com", + }); + + expect(await apl.getAll()).toStrictEqual([]); + }); + }); + }); + }); +}); diff --git a/src/APL/vercelAPL.ts b/src/APL/vercelAPL.ts new file mode 100644 index 0000000..f354491 --- /dev/null +++ b/src/APL/vercelAPL.ts @@ -0,0 +1,116 @@ +/* eslint-disable class-methods-use-this */ +import debugPkg from "debug"; +import fetch from "node-fetch"; + +import { APL, AuthData } from "./apl"; + +const debug = debugPkg.debug("VercelAPL"); + +export const TOKEN_VARIABLE_NAME = "SALEOR_AUTH_TOKEN"; +export const DOMAIN_VARIABLE_NAME = "SALEOR_DOMAIN"; +export const SALEOR_REGISTER_APP_URL = "SALEOR_REGISTER_APP_URL"; +export const SALEOR_DEPLOYMENT_TOKEN = "SALEOR_DEPLOYMENT_TOKEN"; + +const envAuthData = (): AuthData | undefined => { + const token = process.env[TOKEN_VARIABLE_NAME]; + const domain = process.env[DOMAIN_VARIABLE_NAME]; + if (!token || !domain) { + return undefined; + } + return { + token, + domain, + }; +}; + +const saveDataToVercel = async ( + registerAppURL: string, + deploymentToken: string, + authData?: AuthData +) => { + debug("Saving data to Vercel"); + + try { + await fetch(registerAppURL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + token: deploymentToken, + envs: { + [TOKEN_VARIABLE_NAME]: authData?.token || "", + [DOMAIN_VARIABLE_NAME]: authData?.domain || "", + }, + }), + }); + } catch (error) { + debug("Error during saving the data:", error); + } +}; + +export type VercelAPLConfig = { + registerAppURL?: string; + deploymentToken?: string; +}; +/** Vercel APL + * + * Use environment variables for auth data storage. To update data on existing deployment, + * theres Saleor microservice which update new values with the Vercel API and restarts the instance. + * + * This APL should be used for single tenant purposes due to it's limitations: + * - only stores single auth data entry (setting up a new one will overwrite previous values) + * - changing the environment variables require server restart + * + * With this APL we recommend using the [Saleor CLI](https://docs.saleor.io/docs/3.x/cli), + * which automatically set up the required environment variables during deployment: + * - SALEOR_REGISTER_APP_URL: the URL for microservice which set up variables using Vercel API + * - SALEOR_DEPLOYMENT_TOKEN: token for your particular Vercel deployment + */ +export class VercelAPL implements APL { + private registerAppURL: string; + + private deploymentToken: string; + + constructor(config?: VercelAPLConfig) { + const registerAppURL = config?.registerAppURL || process.env.SALEOR_REGISTER_APP_URL; + if (!registerAppURL) { + debug("Missing registerAppURL"); + throw new Error("Misconfiguration: please provide registerAppUrl"); + } + const deploymentToken = config?.deploymentToken || process.env.SALEOR_DEPLOYMENT_TOKEN; + if (!deploymentToken) { + debug("Missing deploymentToken"); + throw new Error("Misconfiguration: please provide deploymentToken"); + } + + this.registerAppURL = registerAppURL; + this.deploymentToken = deploymentToken; + } + + async get(domain: string) { + const authData = envAuthData(); + + if (authData && domain === authData?.domain) { + return authData; + } + return undefined; + } + + async set(authData: AuthData) { + await saveDataToVercel(this.registerAppURL, this.deploymentToken, authData); + } + + async delete(domain: string) { + if (domain === envAuthData()?.domain) { + // Override existing data with the empty values + await saveDataToVercel(this.registerAppURL, this.deploymentToken); + } + } + + async getAll() { + const authData = envAuthData(); + if (!authData) { + return []; + } + return [authData]; + } +} From 10b3171b9dc0bb074cfa845475ceedd4b4015e1b Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 1 Sep 2022 16:36:37 +0200 Subject: [PATCH 03/12] Fix delete test --- src/APL/fileAPL.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/APL/fileAPL.test.ts b/src/APL/fileAPL.test.ts index f4a56c1..c1758c8 100644 --- a/src/APL/fileAPL.test.ts +++ b/src/APL/fileAPL.test.ts @@ -52,10 +52,13 @@ describe("APL", () => { vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); const apl = new FileAPL(); - expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData); + await apl.delete(stubAuthData.domain); + + expect(spyWriteFile).toBeCalledWith(".auth-data.json", "{}"); }); it("Should not delete data when called with unknown domain", async () => { From b2f84b51c823f835087f83c904ca1f1ca02a5447 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 1 Sep 2022 16:43:28 +0200 Subject: [PATCH 04/12] Reduce duplication in the test code --- src/APL/fileAPL.test.ts | 52 ++++++----------------------- src/APL/vercelAPL.test.ts | 70 ++++++++++++--------------------------- 2 files changed, 33 insertions(+), 89 deletions(-) diff --git a/src/APL/fileAPL.test.ts b/src/APL/fileAPL.test.ts index c1758c8..9558f15 100644 --- a/src/APL/fileAPL.test.ts +++ b/src/APL/fileAPL.test.ts @@ -3,6 +3,11 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { FileAPL, loadDataFromFile, saveDataToFile } from "./fileAPL"; +const stubAuthData = { + domain: "example.com", + token: "example-token", +}; + describe("APL", () => { describe("FileAPL", () => { describe("get", () => { @@ -11,11 +16,6 @@ describe("APL", () => { }); it("Returns auth data for existing domain", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); @@ -25,11 +25,6 @@ describe("APL", () => { }); it("Returns undefined for unknown domain", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); @@ -45,11 +40,6 @@ describe("APL", () => { }); it("Should override file when called with known domain", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); @@ -62,11 +52,6 @@ describe("APL", () => { }); it("Should not delete data when called with unknown domain", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); @@ -86,11 +71,6 @@ describe("APL", () => { }); it("Should return list with one item when auth data are existing", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); @@ -118,15 +98,9 @@ describe("APL", () => { it("Save existing auth data to file", async () => { const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); - await saveDataToFile("test.json", { domain: "example.com", token: "example-token" }); + await saveDataToFile("test.json", stubAuthData); - expect(spyWriteFile).toBeCalledWith( - "test.json", - JSON.stringify({ - domain: "example.com", - token: "example-token", - }) - ); + expect(spyWriteFile).toBeCalledWith("test.json", JSON.stringify(stubAuthData)); }); it("Save empty file when no auth data provided", async () => { @@ -151,10 +125,6 @@ describe("APL", () => { it("Load existing auth data", async () => { vi.spyOn(fsPromises, "access").mockResolvedValue(); - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); expect(await loadDataFromFile("test.json")).toStrictEqual(stubAuthData); }); @@ -167,19 +137,19 @@ describe("APL", () => { it("Should return undefined when auth token is missing", async () => { vi.spyOn(fsPromises, "access").mockResolvedValue(); - const stubAuthData = { + const stubInvalidAuthData = { domain: "example.com", }; - vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubInvalidAuthData)); expect(await loadDataFromFile("test.json")).toBe(undefined); }); it("Should return undefined when domain is missing", async () => { vi.spyOn(fsPromises, "access").mockResolvedValue(); - const stubAuthData = { + const stubInvalidAuthData = { token: "token", }; - vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); + vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubInvalidAuthData)); expect(await loadDataFromFile("test.json")).toBe(undefined); }); }); diff --git a/src/APL/vercelAPL.test.ts b/src/APL/vercelAPL.test.ts index b4b3386..320e15d 100644 --- a/src/APL/vercelAPL.test.ts +++ b/src/APL/vercelAPL.test.ts @@ -8,6 +8,16 @@ import { VercelAPL, } from "./vercelAPL"; +const aplConfig = { + deploymentToken: "token", + registerAppURL: "http://example.com", +}; + +const stubAuthData = { + domain: "example.com", + token: "example-token", +}; + describe("APL", () => { describe("VercelAPL", () => { describe("constructor", () => { @@ -31,18 +41,12 @@ describe("APL", () => { }); it("Constructor with config values", async () => { - expect( - () => - new VercelAPL({ - deploymentToken: "token", - registerAppURL: "http://example.com", - }) - ).not.toThrow(); + expect(() => new VercelAPL(aplConfig)).not.toThrow(); }); it("Constructor with config values from environment variables", async () => { - process.env[SALEOR_REGISTER_APP_URL] = "http://example.com"; - process.env[SALEOR_DEPLOYMENT_TOKEN] = "token"; + process.env[SALEOR_REGISTER_APP_URL] = aplConfig.registerAppURL; + process.env[SALEOR_DEPLOYMENT_TOKEN] = aplConfig.deploymentToken; expect(() => new VercelAPL()).not.toThrow(); }); @@ -56,47 +60,28 @@ describe("APL", () => { }); it("Read existing auth data", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; - const apl = new VercelAPL({ - deploymentToken: "token", - registerAppURL: "http://example.com", - }); + const apl = new VercelAPL(); expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData); }); it("Return undefined when unknown domain requested", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; - const apl = new VercelAPL({ - deploymentToken: "token", - registerAppURL: "http://example.com", - }); + const apl = new VercelAPL(aplConfig); expect(await apl.get("unknown-domain.example.com")).toBe(undefined); }); it("Return undefined when no data is defined", async () => { - process.env[TOKEN_VARIABLE_NAME] = undefined; - process.env[DOMAIN_VARIABLE_NAME] = undefined; + delete process.env[TOKEN_VARIABLE_NAME]; + delete process.env[DOMAIN_VARIABLE_NAME]; - const apl = new VercelAPL({ - deploymentToken: "token", - registerAppURL: "http://example.com", - }); + const apl = new VercelAPL(aplConfig); expect(await apl.get("example.com")).toBe(undefined); }); @@ -112,30 +97,19 @@ describe("APL", () => { }); it("Read existing auth data", async () => { - const stubAuthData = { - domain: "example.com", - token: "example-token", - }; - process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; - const apl = new VercelAPL({ - deploymentToken: "token", - registerAppURL: "http://example.com", - }); + const apl = new VercelAPL(aplConfig); expect(await apl.getAll()).toStrictEqual([stubAuthData]); }); it("Return empty list when no auth data are existing", async () => { - process.env[TOKEN_VARIABLE_NAME] = undefined; - process.env[DOMAIN_VARIABLE_NAME] = undefined; + delete process.env[TOKEN_VARIABLE_NAME]; + delete process.env[DOMAIN_VARIABLE_NAME]; - const apl = new VercelAPL({ - deploymentToken: "token", - registerAppURL: "http://example.com", - }); + const apl = new VercelAPL(aplConfig); expect(await apl.getAll()).toStrictEqual([]); }); From 5b7bb5d878920f121da465b10ffcc412d83cbe53 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 1 Sep 2022 17:18:32 +0200 Subject: [PATCH 05/12] Add util function docstring --- src/APL/fileAPL.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/APL/fileAPL.ts b/src/APL/fileAPL.ts index d9d162f..b850606 100644 --- a/src/APL/fileAPL.ts +++ b/src/APL/fileAPL.ts @@ -5,6 +5,12 @@ import { APL, AuthData } from "./apl"; const debug = debugPkg.debug("FileAPL"); +/** + * Load auth data from a file and return it as AuthData format. + * In case of incomplete or invalid data, return `undefined`. + * + * @param {string} fileName + */ export const loadDataFromFile = async (fileName: string): Promise => { debug(`Load auth data from the ${fileName} file`); let parsedData: Record = {}; @@ -22,6 +28,13 @@ export const loadDataFromFile = async (fileName: string): Promise { debug(`Save auth data to the ${fileName} file`); const newData = authData ? JSON.stringify(authData) : "{}"; From 6adabd1cba2652d7a91bf56313ccbda9098540b3 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 1 Sep 2022 17:23:19 +0200 Subject: [PATCH 06/12] Introduce debug namespaces --- src/APL/fileAPL.ts | 2 +- src/APL/vercelAPL.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/APL/fileAPL.ts b/src/APL/fileAPL.ts index b850606..38a60e4 100644 --- a/src/APL/fileAPL.ts +++ b/src/APL/fileAPL.ts @@ -3,7 +3,7 @@ import { promises as fsPromises } from "fs"; import { APL, AuthData } from "./apl"; -const debug = debugPkg.debug("FileAPL"); +const debug = debugPkg.debug("app-sdk:FileAPL"); /** * Load auth data from a file and return it as AuthData format. diff --git a/src/APL/vercelAPL.ts b/src/APL/vercelAPL.ts index f354491..00faf95 100644 --- a/src/APL/vercelAPL.ts +++ b/src/APL/vercelAPL.ts @@ -4,7 +4,7 @@ import fetch from "node-fetch"; import { APL, AuthData } from "./apl"; -const debug = debugPkg.debug("VercelAPL"); +const debug = debugPkg.debug("app-sdk:VercelAPL"); export const TOKEN_VARIABLE_NAME = "SALEOR_AUTH_TOKEN"; export const DOMAIN_VARIABLE_NAME = "SALEOR_DOMAIN"; From ace6efbdba20c61c9b24a65e3eb49ecb370dcd33 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 1 Sep 2022 18:40:58 +0200 Subject: [PATCH 07/12] Change default file name for FileAPL --- src/APL/fileAPL.test.ts | 2 +- src/APL/fileAPL.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/APL/fileAPL.test.ts b/src/APL/fileAPL.test.ts index 9558f15..280107b 100644 --- a/src/APL/fileAPL.test.ts +++ b/src/APL/fileAPL.test.ts @@ -48,7 +48,7 @@ describe("APL", () => { await apl.delete(stubAuthData.domain); - expect(spyWriteFile).toBeCalledWith(".auth-data.json", "{}"); + expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", "{}"); }); it("Should not delete data when called with unknown domain", async () => { diff --git a/src/APL/fileAPL.ts b/src/APL/fileAPL.ts index 38a60e4..5699238 100644 --- a/src/APL/fileAPL.ts +++ b/src/APL/fileAPL.ts @@ -65,7 +65,7 @@ export class FileAPL implements APL { private fileName: string; constructor(config: FileAPLConfig = {}) { - this.fileName = config?.fileName || ".auth-data.json"; + this.fileName = config?.fileName || ".saleor-app-auth.json"; } async get(domain: string) { From 3b81151ccaf05a8265dd6da991a75d3cee15c34b Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 1 Sep 2022 18:55:16 +0200 Subject: [PATCH 08/12] Rename helper function --- src/APL/vercelAPL.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/APL/vercelAPL.ts b/src/APL/vercelAPL.ts index 00faf95..1d425c9 100644 --- a/src/APL/vercelAPL.ts +++ b/src/APL/vercelAPL.ts @@ -11,7 +11,7 @@ export const DOMAIN_VARIABLE_NAME = "SALEOR_DOMAIN"; export const SALEOR_REGISTER_APP_URL = "SALEOR_REGISTER_APP_URL"; export const SALEOR_DEPLOYMENT_TOKEN = "SALEOR_DEPLOYMENT_TOKEN"; -const envAuthData = (): AuthData | undefined => { +const getEnvAuth = (): AuthData | undefined => { const token = process.env[TOKEN_VARIABLE_NAME]; const domain = process.env[DOMAIN_VARIABLE_NAME]; if (!token || !domain) { @@ -87,7 +87,7 @@ export class VercelAPL implements APL { } async get(domain: string) { - const authData = envAuthData(); + const authData = getEnvAuth(); if (authData && domain === authData?.domain) { return authData; @@ -100,14 +100,14 @@ export class VercelAPL implements APL { } async delete(domain: string) { - if (domain === envAuthData()?.domain) { + if (domain === getEnvAuth()?.domain) { // Override existing data with the empty values await saveDataToVercel(this.registerAppURL, this.deploymentToken); } } async getAll() { - const authData = envAuthData(); + const authData = getEnvAuth(); if (!authData) { return []; } From 01cd1dc525db51290ee2039def7c14ff04319fb2 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Fri, 2 Sep 2022 14:07:33 +0200 Subject: [PATCH 09/12] Update APL documentation --- docs/apl.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/docs/apl.md b/docs/apl.md index f6acb68..1a09968 100644 --- a/docs/apl.md +++ b/docs/apl.md @@ -14,7 +14,7 @@ APL is an interface for managing auth data of registered Apps. Implementing it d ## Example implementation -Let's create an APL, which uses redis for data storage: +Let's create an APL, which uses Redis for data storage: ```ts import { createClient } from "redis"; @@ -66,3 +66,74 @@ const handler = async (request) => { // the middleware will reject request if it's domain has not been registered export default withRegisteredSaleorDomainHeader({ apl: redisAPL })(handler); ``` + +### Using different APL depending on the environment + +Depending on the environment your app is working on, you may want to use a different APL. For example during local development you might like to use `FileAPL`, because it does not require any additional infrastructure. Deployed apps on the other hand need more robust solution. + +To handle both scenarios, initialize the proper APLs in your code based on it's environment. In your application code: + +```ts +// lib/saleorApp.ts + +import { FileAPL, VercelAPL } from "@saleor/app-sdk/APL"; + +// Based on environment variable the app will use a different APL: +// - For local development store auth data in the `.auth-data.json`. +// - For app deployment on Vercel with Saleor CLI, use vercelAPL. +export const apl = process.env.VERCEL === "1" ? new VercelAPL() : new FileAPL(); +``` + +Now you can use it for in your view: + +```ts +import { SALEOR_DOMAIN_HEADER } from "@saleor/app-sdk/const"; +import { withRegisteredSaleorDomainHeader } from "@saleor/app-sdk/middleware"; +import type { Handler } from "retes"; +import { toNextHandler } from "retes/adapter"; +import { Response } from "retes/response"; + +// import created APL +import { apl } from "@lib/saleorApp"; + +const handler: Handler = async (request) => { + const saleorDomain = request.headers[SALEOR_DOMAIN_HEADER]; + + // Get auth data + const authData = apl.get(saleorDomain); + + // view logic... + + return Response.OK(); +}; + +export default toNextHandler([withRegisteredSaleorDomainHeader({ apl }), handler]); +``` + +## Available APLs + +### FileAPL + +File based storage of auth data, intended for local development. Data are stored in the `.saleor-app-auth.json` file. You'll be able to develop app without additional dependencies or infrastructure. + +Please note: this APL supports single tenant only (new registrations overwrite previous ones) and should not be used on production. + +### VercelAPL + +Single tenant APL dedicated for apps deployed on Vercel. Apps deployed from Marketplace and CLI automatically set up Vercel project for this APL (`SALEOR_REGISTER_APP_URL` and `SALEOR_DEPLOYMENT_TOKEN` variables). + +The auth data are stored using environment variables: + +```mermaid +sequenceDiagram + participant SI as Saleor Instance + participant A as App + participant SSI as Saleor x Vercel integration + participant V as Vercel + + SI->>+A: Register + A->>SSI: Update auth data + A->>-SI: Register completed + SSI->>V: Set auth data as environment variables + V->>A: Redeploy the application +``` From 2fc57718a687e57dbc2e8cda0d9ff6b5fe6ee64f Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Fri, 2 Sep 2022 15:39:15 +0200 Subject: [PATCH 10/12] Code review changes --- src/APL/fileAPL.test.ts | 114 +++++++++++++------------------------- src/APL/fileAPL.ts | 91 +++++++++++++++--------------- src/APL/vercelAPL.test.ts | 81 +++++++++++++-------------- src/APL/vercelAPL.ts | 70 ++++++++++++----------- 4 files changed, 155 insertions(+), 201 deletions(-) diff --git a/src/APL/fileAPL.test.ts b/src/APL/fileAPL.test.ts index 280107b..84c4d73 100644 --- a/src/APL/fileAPL.test.ts +++ b/src/APL/fileAPL.test.ts @@ -1,7 +1,7 @@ import { promises as fsPromises } from "fs"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { FileAPL, loadDataFromFile, saveDataToFile } from "./fileAPL"; +import { FileAPL } from "./fileAPL"; const stubAuthData = { domain: "example.com", @@ -9,10 +9,20 @@ const stubAuthData = { }; describe("APL", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + describe("FileAPL", () => { describe("get", () => { - afterEach(() => { - vi.clearAllMocks(); + it("Should throw error when JSON parse fails", async () => { + vi.spyOn(fsPromises, "access").mockResolvedValue(); + vi.spyOn(fsPromises, "readFile").mockResolvedValue("Not a valid JSON"); + + const apl = new FileAPL(); + await expect(apl.get(stubAuthData.domain)).rejects.toThrow( + "File APL could not read auth data from the .saleor-app-auth.json file" + ); }); it("Returns auth data for existing domain", async () => { @@ -34,11 +44,32 @@ describe("APL", () => { }); }); - describe("delete", () => { - afterEach(() => { - vi.clearAllMocks(); - }); + describe("set", () => { + it("Handle write file errors", async () => { + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockImplementation(() => { + throw Error("Write error"); + }); + const apl = new FileAPL(); + + await expect(apl.set(stubAuthData)).rejects.toThrow( + "File APL was unable to save auth data" + ); + expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", JSON.stringify(stubAuthData)); + }); + }); + + it("Successfully save to file", async () => { + const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); + + const apl = new FileAPL(); + + await expect(apl.set(stubAuthData)); + + expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", JSON.stringify(stubAuthData)); + }); + + describe("delete", () => { it("Should override file when called with known domain", async () => { vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); @@ -66,10 +97,6 @@ describe("APL", () => { }); describe("getAll", () => { - afterEach(() => { - vi.clearAllMocks(); - }); - it("Should return list with one item when auth data are existing", async () => { vi.spyOn(fsPromises, "access").mockResolvedValue(); vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); @@ -89,69 +116,4 @@ describe("APL", () => { }); }); }); - - describe("FileAPL utils", () => { - describe("saveDataToFile", () => { - afterEach(() => { - vi.clearAllMocks(); - }); - - it("Save existing auth data to file", async () => { - const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); - await saveDataToFile("test.json", stubAuthData); - - expect(spyWriteFile).toBeCalledWith("test.json", JSON.stringify(stubAuthData)); - }); - - it("Save empty file when no auth data provided", async () => { - const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockResolvedValue(); - await saveDataToFile("test.json"); - expect(spyWriteFile).toBeCalledWith("test.json", "{}"); - }); - - it("Handle write file errors", async () => { - const spyWriteFile = vi.spyOn(fsPromises, "writeFile").mockImplementation(() => { - throw Error("Write error"); - }); - await saveDataToFile("test.json"); - expect(spyWriteFile).toBeCalledWith("test.json", "{}"); - }); - }); - - describe("loadDataFromFile", () => { - afterEach(() => { - vi.clearAllMocks(); - }); - - it("Load existing auth data", async () => { - vi.spyOn(fsPromises, "access").mockResolvedValue(); - vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubAuthData)); - expect(await loadDataFromFile("test.json")).toStrictEqual(stubAuthData); - }); - - it("Should return undefined when JSON parse fails", async () => { - vi.spyOn(fsPromises, "access").mockResolvedValue(); - vi.spyOn(fsPromises, "readFile").mockResolvedValue("Not a valid JSON"); - expect(await loadDataFromFile("test.json")).toBe(undefined); - }); - - it("Should return undefined when auth token is missing", async () => { - vi.spyOn(fsPromises, "access").mockResolvedValue(); - const stubInvalidAuthData = { - domain: "example.com", - }; - vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubInvalidAuthData)); - expect(await loadDataFromFile("test.json")).toBe(undefined); - }); - - it("Should return undefined when domain is missing", async () => { - vi.spyOn(fsPromises, "access").mockResolvedValue(); - const stubInvalidAuthData = { - token: "token", - }; - vi.spyOn(fsPromises, "readFile").mockResolvedValue(JSON.stringify(stubInvalidAuthData)); - expect(await loadDataFromFile("test.json")).toBe(undefined); - }); - }); - }); }); diff --git a/src/APL/fileAPL.ts b/src/APL/fileAPL.ts index 5699238..a1653d7 100644 --- a/src/APL/fileAPL.ts +++ b/src/APL/fileAPL.ts @@ -5,46 +5,6 @@ import { APL, AuthData } from "./apl"; const debug = debugPkg.debug("app-sdk:FileAPL"); -/** - * Load auth data from a file and return it as AuthData format. - * In case of incomplete or invalid data, return `undefined`. - * - * @param {string} fileName - */ -export const loadDataFromFile = async (fileName: string): Promise => { - debug(`Load auth data from the ${fileName} file`); - let parsedData: Record = {}; - try { - await fsPromises.access(fileName); - parsedData = JSON.parse(await fsPromises.readFile(fileName, "utf-8")); - } catch (err) { - debug(`Could not read auth data from the ${fileName} file`, err); - return undefined; - } - const { token, domain } = parsedData; - if (token && domain) { - return { token, domain }; - } - return undefined; -}; - -/** - * Save auth data to file. - * When `authData` argument is empty, will overwrite file with empty values. - * - * @param {string} fileName - * @param {AuthData} [authData] - */ -export const saveDataToFile = async (fileName: string, authData?: AuthData) => { - debug(`Save auth data to the ${fileName} file`); - const newData = authData ? JSON.stringify(authData) : "{}"; - try { - await fsPromises.writeFile(fileName, newData); - } catch (err) { - debug(`Could not save auth data to the ${fileName} file`, err); - } -}; - export type FileAPLConfig = { fileName?: string; }; @@ -68,8 +28,49 @@ export class FileAPL implements APL { this.fileName = config?.fileName || ".saleor-app-auth.json"; } + /** + * Load auth data from a file and return it as AuthData format. + * In case of incomplete or invalid data, return `undefined`. + * + * @param {string} fileName + */ + private async loadDataFromFile(): Promise { + debug(`Load auth data from the ${this.fileName} file`); + let parsedData: Record = {}; + try { + await fsPromises.access(this.fileName); + parsedData = JSON.parse(await fsPromises.readFile(this.fileName, "utf-8")); + } catch (err) { + debug(`Could not read auth data from the ${this.fileName} file`, err); + throw new Error(`File APL could not read auth data from the ${this.fileName} file`); + } + const { token, domain } = parsedData; + if (token && domain) { + return { token, domain }; + } + return undefined; + } + + /** + * Save auth data to file. + * When `authData` argument is empty, will overwrite file with empty values. + * + * @param {string} fileName + * @param {AuthData} [authData] + */ + private async saveDataToFile(authData?: AuthData) { + debug(`Save auth data to the ${this.fileName} file`); + const newData = authData ? JSON.stringify(authData) : "{}"; + try { + await fsPromises.writeFile(this.fileName, newData); + } catch (err) { + debug(`Could not save auth data to the ${this.fileName} file`, err); + throw new Error("File APL was unable to save auth data"); + } + } + async get(domain: string) { - const authData = await loadDataFromFile(this.fileName); + const authData = await this.loadDataFromFile(); if (domain === authData?.domain) { return authData; } @@ -77,19 +78,19 @@ export class FileAPL implements APL { } async set(authData: AuthData) { - await saveDataToFile(this.fileName, authData); + await this.saveDataToFile(authData); } async delete(domain: string) { - const authData = await loadDataFromFile(this.fileName); + const authData = await this.loadDataFromFile(); if (domain === authData?.domain) { - await saveDataToFile(this.fileName); + await this.saveDataToFile(); } } async getAll() { - const authData = await loadDataFromFile(this.fileName); + const authData = await this.loadDataFromFile(); if (!authData) { return []; diff --git a/src/APL/vercelAPL.test.ts b/src/APL/vercelAPL.test.ts index 320e15d..af55b4e 100644 --- a/src/APL/vercelAPL.test.ts +++ b/src/APL/vercelAPL.test.ts @@ -1,12 +1,6 @@ import { afterEach, describe, expect, it } from "vitest"; -import { - DOMAIN_VARIABLE_NAME, - SALEOR_DEPLOYMENT_TOKEN, - SALEOR_REGISTER_APP_URL, - TOKEN_VARIABLE_NAME, - VercelAPL, -} from "./vercelAPL"; +import { VercelAPL, VercelAPLVariables } from "./vercelAPL"; const aplConfig = { deploymentToken: "token", @@ -19,58 +13,63 @@ const stubAuthData = { }; describe("APL", () => { + const initialEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...initialEnv }; + }); + describe("VercelAPL", () => { describe("constructor", () => { - const initialEnv = { ...process.env }; - - afterEach(() => { - process.env = { ...initialEnv }; - }); - it("Raise an error when configuration is missing", async () => { - delete process.env[SALEOR_REGISTER_APP_URL]; - process.env[SALEOR_DEPLOYMENT_TOKEN] = "token"; + delete process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL]; + process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN] = "token"; expect(() => new VercelAPL()).toThrow(); - process.env[SALEOR_REGISTER_APP_URL] = "http://example.com"; - delete process.env[SALEOR_DEPLOYMENT_TOKEN]; + process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL] = "http://example.com"; + delete process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN]; expect(() => new VercelAPL()).toThrow(); }); }); - it("Constructor with config values", async () => { + it("Constructs VercelAPL instance when deploymentToken and registerAppURL provided", async () => { expect(() => new VercelAPL(aplConfig)).not.toThrow(); }); - it("Constructor with config values from environment variables", async () => { - process.env[SALEOR_REGISTER_APP_URL] = aplConfig.registerAppURL; - process.env[SALEOR_DEPLOYMENT_TOKEN] = aplConfig.deploymentToken; + it("Constructs VercelAPL instance with config values from environment variables", async () => { + process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL] = aplConfig.registerAppURL; + process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN] = aplConfig.deploymentToken; expect(() => new VercelAPL()).not.toThrow(); }); + it("Test if constructor use options over environment variables", async () => { + process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL] = "environment"; + process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN] = "environment"; + + const apl = await new VercelAPL({ deploymentToken: "option", registerAppURL: "option" }); + // eslint-disable-next-line dot-notation + expect(apl["deploymentToken"]).toBe("option"); + // eslint-disable-next-line dot-notation + expect(apl["registerAppURL"]).toBe("option"); + }); + describe("get", () => { describe("Read existing auth data from env", () => { - const initialEnv = { ...process.env }; - - afterEach(() => { - process.env = { ...initialEnv }; - }); - it("Read existing auth data", async () => { - process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; - process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; + process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME] = stubAuthData.token; + process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME] = stubAuthData.domain; - const apl = new VercelAPL(); + const apl = new VercelAPL(aplConfig); expect(await apl.get(stubAuthData.domain)).toStrictEqual(stubAuthData); }); it("Return undefined when unknown domain requested", async () => { - process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; - process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; + process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME] = stubAuthData.token; + process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME] = stubAuthData.domain; const apl = new VercelAPL(aplConfig); @@ -78,8 +77,8 @@ describe("APL", () => { }); it("Return undefined when no data is defined", async () => { - delete process.env[TOKEN_VARIABLE_NAME]; - delete process.env[DOMAIN_VARIABLE_NAME]; + delete process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME]; + delete process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME]; const apl = new VercelAPL(aplConfig); @@ -90,15 +89,9 @@ describe("APL", () => { describe("getAll", () => { describe("Read existing auth data from env", () => { - const initialEnv = { ...process.env }; - - afterEach(() => { - process.env = { ...initialEnv }; - }); - it("Read existing auth data", async () => { - process.env[TOKEN_VARIABLE_NAME] = stubAuthData.token; - process.env[DOMAIN_VARIABLE_NAME] = stubAuthData.domain; + process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME] = stubAuthData.token; + process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME] = stubAuthData.domain; const apl = new VercelAPL(aplConfig); @@ -106,8 +99,8 @@ describe("APL", () => { }); it("Return empty list when no auth data are existing", async () => { - delete process.env[TOKEN_VARIABLE_NAME]; - delete process.env[DOMAIN_VARIABLE_NAME]; + delete process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME]; + delete process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME]; const apl = new VercelAPL(aplConfig); diff --git a/src/APL/vercelAPL.ts b/src/APL/vercelAPL.ts index 1d425c9..6595182 100644 --- a/src/APL/vercelAPL.ts +++ b/src/APL/vercelAPL.ts @@ -6,14 +6,16 @@ import { APL, AuthData } from "./apl"; const debug = debugPkg.debug("app-sdk:VercelAPL"); -export const TOKEN_VARIABLE_NAME = "SALEOR_AUTH_TOKEN"; -export const DOMAIN_VARIABLE_NAME = "SALEOR_DOMAIN"; -export const SALEOR_REGISTER_APP_URL = "SALEOR_REGISTER_APP_URL"; -export const SALEOR_DEPLOYMENT_TOKEN = "SALEOR_DEPLOYMENT_TOKEN"; +export const VercelAPLVariables = { + TOKEN_VARIABLE_NAME: "SALEOR_AUTH_TOKEN", + DOMAIN_VARIABLE_NAME: "SALEOR_DOMAIN", + SALEOR_REGISTER_APP_URL: "SALEOR_REGISTER_APP_URL", + SALEOR_DEPLOYMENT_TOKEN: "SALEOR_DEPLOYMENT_TOKEN", +}; const getEnvAuth = (): AuthData | undefined => { - const token = process.env[TOKEN_VARIABLE_NAME]; - const domain = process.env[DOMAIN_VARIABLE_NAME]; + const token = process.env[VercelAPLVariables.TOKEN_VARIABLE_NAME]; + const domain = process.env[VercelAPLVariables.DOMAIN_VARIABLE_NAME]; if (!token || !domain) { return undefined; } @@ -23,30 +25,6 @@ const getEnvAuth = (): AuthData | undefined => { }; }; -const saveDataToVercel = async ( - registerAppURL: string, - deploymentToken: string, - authData?: AuthData -) => { - debug("Saving data to Vercel"); - - try { - await fetch(registerAppURL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - token: deploymentToken, - envs: { - [TOKEN_VARIABLE_NAME]: authData?.token || "", - [DOMAIN_VARIABLE_NAME]: authData?.domain || "", - }, - }), - }); - } catch (error) { - debug("Error during saving the data:", error); - } -}; - export type VercelAPLConfig = { registerAppURL?: string; deploymentToken?: string; @@ -71,14 +49,14 @@ export class VercelAPL implements APL { private deploymentToken: string; constructor(config?: VercelAPLConfig) { - const registerAppURL = config?.registerAppURL || process.env.SALEOR_REGISTER_APP_URL; + const registerAppURL = + config?.registerAppURL || process.env[VercelAPLVariables.SALEOR_REGISTER_APP_URL]; if (!registerAppURL) { - debug("Missing registerAppURL"); throw new Error("Misconfiguration: please provide registerAppUrl"); } - const deploymentToken = config?.deploymentToken || process.env.SALEOR_DEPLOYMENT_TOKEN; + const deploymentToken = + config?.deploymentToken || process.env[VercelAPLVariables.SALEOR_DEPLOYMENT_TOKEN]; if (!deploymentToken) { - debug("Missing deploymentToken"); throw new Error("Misconfiguration: please provide deploymentToken"); } @@ -86,6 +64,26 @@ export class VercelAPL implements APL { this.deploymentToken = deploymentToken; } + private async saveDataToVercel(authData?: AuthData) { + debug(`saveDataToVercel with: ${authData}`); + try { + await fetch(this.registerAppURL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + token: this.deploymentToken, + envs: { + [VercelAPLVariables.TOKEN_VARIABLE_NAME]: authData?.token || "", + [VercelAPLVariables.DOMAIN_VARIABLE_NAME]: authData?.domain || "", + }, + }), + }); + } catch (error) { + debug("Error during saving the data:", error); + throw new Error(`VercelAPL was not able to save auth data${error}`); + } + } + async get(domain: string) { const authData = getEnvAuth(); @@ -96,13 +94,13 @@ export class VercelAPL implements APL { } async set(authData: AuthData) { - await saveDataToVercel(this.registerAppURL, this.deploymentToken, authData); + await this.saveDataToVercel(authData); } async delete(domain: string) { if (domain === getEnvAuth()?.domain) { // Override existing data with the empty values - await saveDataToVercel(this.registerAppURL, this.deploymentToken); + await this.saveDataToVercel(); } } From 668b96ac4c176bfaad1d81400a2c815324e1b071 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Mon, 5 Sep 2022 10:31:31 +0200 Subject: [PATCH 11/12] Update names to kebab case --- src/APL/{fileAPL.test.ts => file-apl.test.ts} | 2 +- src/APL/{fileAPL.ts => file-apl.ts} | 0 src/APL/index.ts | 4 ++-- src/APL/{vercelAPL.test.ts => vercel-apl.test.ts} | 2 +- src/APL/{vercelAPL.ts => vercel-apl.ts} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/APL/{fileAPL.test.ts => file-apl.test.ts} (99%) rename src/APL/{fileAPL.ts => file-apl.ts} (100%) rename src/APL/{vercelAPL.test.ts => vercel-apl.test.ts} (98%) rename src/APL/{vercelAPL.ts => vercel-apl.ts} (100%) diff --git a/src/APL/fileAPL.test.ts b/src/APL/file-apl.test.ts similarity index 99% rename from src/APL/fileAPL.test.ts rename to src/APL/file-apl.test.ts index 84c4d73..70eee80 100644 --- a/src/APL/fileAPL.test.ts +++ b/src/APL/file-apl.test.ts @@ -1,7 +1,7 @@ import { promises as fsPromises } from "fs"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { FileAPL } from "./fileAPL"; +import { FileAPL } from "./file-apl"; const stubAuthData = { domain: "example.com", diff --git a/src/APL/fileAPL.ts b/src/APL/file-apl.ts similarity index 100% rename from src/APL/fileAPL.ts rename to src/APL/file-apl.ts diff --git a/src/APL/index.ts b/src/APL/index.ts index 6d54627..0598c52 100644 --- a/src/APL/index.ts +++ b/src/APL/index.ts @@ -1,3 +1,3 @@ export * from "./apl"; -export * from "./fileAPL"; -export * from "./vercelAPL"; +export * from "./file-apl"; +export * from "./vercel-apl"; diff --git a/src/APL/vercelAPL.test.ts b/src/APL/vercel-apl.test.ts similarity index 98% rename from src/APL/vercelAPL.test.ts rename to src/APL/vercel-apl.test.ts index af55b4e..1938898 100644 --- a/src/APL/vercelAPL.test.ts +++ b/src/APL/vercel-apl.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from "vitest"; -import { VercelAPL, VercelAPLVariables } from "./vercelAPL"; +import { VercelAPL, VercelAPLVariables } from "./vercel-apl"; const aplConfig = { deploymentToken: "token", diff --git a/src/APL/vercelAPL.ts b/src/APL/vercel-apl.ts similarity index 100% rename from src/APL/vercelAPL.ts rename to src/APL/vercel-apl.ts From 027b23911426578aedfa986feb669e7f82cf153b Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Mon, 5 Sep 2022 11:09:06 +0200 Subject: [PATCH 12/12] Use APL debug util --- docs/debugging.md | 1 + src/APL/apl-debug.ts | 3 +++ src/APL/file-apl.ts | 4 ++-- src/APL/vercel-apl.ts | 4 ++-- src/middleware/middleware-debug.ts | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/APL/apl-debug.ts diff --git a/docs/debugging.md b/docs/debugging.md index cd8c5f8..3df6273 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -50,3 +50,4 @@ Use the namespace name to enable debug logs for each module. | \app-sdk:\* | Enable all | | app-sdk:AppBridge | Enable [AppBridge](./app-bridge.md) (browser only) | | app-sdk:Middleware:\* | Enable all middlewares (node only) | +| app-sdk:APL:\* | Enable all APLs (node only) | diff --git a/src/APL/apl-debug.ts b/src/APL/apl-debug.ts new file mode 100644 index 0000000..e25b627 --- /dev/null +++ b/src/APL/apl-debug.ts @@ -0,0 +1,3 @@ +import { createDebug } from "../debug"; + +export const createAPLDebug = (namespace: string) => createDebug(`APL:${namespace}`); diff --git a/src/APL/file-apl.ts b/src/APL/file-apl.ts index a1653d7..4f52b9e 100644 --- a/src/APL/file-apl.ts +++ b/src/APL/file-apl.ts @@ -1,9 +1,9 @@ -import debugPkg from "debug"; import { promises as fsPromises } from "fs"; import { APL, AuthData } from "./apl"; +import { createAPLDebug } from "./apl-debug"; -const debug = debugPkg.debug("app-sdk:FileAPL"); +const debug = createAPLDebug("FileAPL"); export type FileAPLConfig = { fileName?: string; diff --git a/src/APL/vercel-apl.ts b/src/APL/vercel-apl.ts index 6595182..a529e25 100644 --- a/src/APL/vercel-apl.ts +++ b/src/APL/vercel-apl.ts @@ -1,10 +1,10 @@ /* eslint-disable class-methods-use-this */ -import debugPkg from "debug"; import fetch from "node-fetch"; import { APL, AuthData } from "./apl"; +import { createAPLDebug } from "./apl-debug"; -const debug = debugPkg.debug("app-sdk:VercelAPL"); +const debug = createAPLDebug("VercelAPL"); export const VercelAPLVariables = { TOKEN_VARIABLE_NAME: "SALEOR_AUTH_TOKEN", diff --git a/src/middleware/middleware-debug.ts b/src/middleware/middleware-debug.ts index 239954b..a44ea0e 100644 --- a/src/middleware/middleware-debug.ts +++ b/src/middleware/middleware-debug.ts @@ -5,7 +5,7 @@ import { createDebug } from "../debug"; export const createMiddlewareDebug = (middleware: string) => createDebug(`Middleware:${middleware}`); -type DebugFactory = (handlerName: string) => (msg: string, ...args: any[]) => void; +type DebugFactory = (handlerName: string) => (msg: string, ...args: unknown[]) => void; /** * Experimental. Needs to be tested and evaluated on security