Split APL into single purpose ones, fix module export issue
This commit is contained in:
parent
18bdeff68b
commit
b41f54ad4c
9 changed files with 537 additions and 124 deletions
|
@ -9,7 +9,7 @@
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"prettier" // prettier *has* to be the last one, to avoid conflicting rules
|
"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"],
|
"plugins": ["simple-import-sort", "@typescript-eslint"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||||
|
|
|
@ -3,3 +3,4 @@ saleor/api.tsx
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
graphql.schema.json
|
graphql.schema.json
|
||||||
lib/$path.ts
|
lib/$path.ts
|
||||||
|
dist
|
|
@ -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];
|
|
||||||
},
|
|
||||||
};
|
|
184
src/APL/fileAPL.test.ts
Normal file
184
src/APL/fileAPL.test.ts
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
87
src/APL/fileAPL.ts
Normal file
87
src/APL/fileAPL.ts
Normal file
|
@ -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<AuthData | undefined> => {
|
||||||
|
debug(`Load auth data from the ${fileName} file`);
|
||||||
|
let parsedData: Record<string, string> = {};
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./apl";
|
export * from "./apl";
|
||||||
export * from "./environmentVariablesAPL";
|
export * from "./fileAPL";
|
||||||
|
export * from "./vercelAPL";
|
||||||
|
|
145
src/APL/vercelAPL.test.ts
Normal file
145
src/APL/vercelAPL.test.ts
Normal file
|
@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
116
src/APL/vercelAPL.ts
Normal file
116
src/APL/vercelAPL.ts
Normal file
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue