Skip to content

Commit

Permalink
Refactor how to load .env file (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroki0525 authored Nov 4, 2023
1 parent a5ec2ee commit 7405dab
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 131 deletions.
8 changes: 8 additions & 0 deletions .changeset/flat-windows-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@dandori/core": patch
"@dandori/libs": patch
"@dandori/cli": patch
"@dandori/ui": patch
---

refactor how to load .env file
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-imports": "warn",
"unused-imports/no-unused-vars": [
"warn",
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
],
"arrow-body-style": ["error", "as-needed"]
// avoid warning `import OpenAI from "openai"`;
"import/no-named-as-default": "off"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-unused-imports": "3.0.0",
"eslint-plugin-vitest": "^0.3.8",
"eslint-plugin-vitest": "^0.3.9",
"husky": "^8.0.3",
"lint-staged": "^14.0.1",
"prettier": "3.0.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/core/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ vi.mock("@dandori/core", () => ({
}));

describe("DandoriCoreCli", () => {
const mockConsole = vi.spyOn(console, "log");
const mockConsole = vi.spyOn(console, "log").mockImplementation(() => {});
const inputFileName = "DandoriCoreCli.txt";
const inputFileText = "DandoriCoreCli";
const loadProcessArgv = (options: string[]) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import generateDandoriTasks, {
OptionalTaskPropsOption,
} from "@dandori/core";
import { readFile } from "fs/promises";
import { generateDandoriFilePath } from "@dandori/libs";
import { loadFile } from "@dandori/libs";

export default class DandoriCoreCli {
private inputFile: string = "";
Expand Down Expand Up @@ -44,7 +44,7 @@ export default class DandoriCoreCli {

protected async generateDandoriTasks(): Promise<DandoriTask[]> {
this.program.parse(process.argv);
const source = await readFile(generateDandoriFilePath(this.inputFile));
const source = await readFile(loadFile(this.inputFile));
const { envFile, optionalTaskProps, model } = this.program.opts<{
envFile?: string;
optionalTaskProps?: string;
Expand Down
3 changes: 1 addition & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"license": "MIT",
"dependencies": {
"@dandori/libs": "workspace:*",
"dotenv": "^16.3.1",
"openai": "^4.14.2"
"openai": "^4.15.0"
}
}
88 changes: 10 additions & 78 deletions packages/core/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import { mkdir, rm, rmdir, writeFile } from "fs/promises";
import generateDandoriTasks, {
ChatGPTFunctionCallModel,
DandoriTaskOptionalProperty,
DandoriTaskProperty,
DandoriTaskRequiredProperty,
OptionalAllDandoriTaskPropertiesName,
} from "../index";
import {
describe,
beforeEach,
beforeAll,
afterEach,
afterAll,
it,
vi,
expect,
Mock,
} from "vitest";
import { describe, beforeEach, afterEach, it, vi, expect, Mock } from "vitest";
import OpenAI from "openai";
import { logger, runPromisesSequentially } from "@dandori/libs";
import { loadEnvFile, logger, runPromisesSequentially } from "@dandori/libs";

const openAiResArguments = { tasks: [] } as const;
vi.mock("openai", () => {
Expand Down Expand Up @@ -54,10 +43,12 @@ vi.mock("@dandori/libs", async () => {
runPromisesSequentially: vi.fn((runPromises, _runningLogPrefix) =>
Promise.all(runPromises.map((runPromise: () => any) => runPromise())),
),
loadEnvFile: vi.fn(),
};
});

const runPromisesSequentiallyMock = runPromisesSequentially as Mock;
const loadEnvFileMock = loadEnvFile as Mock;

describe("generateDandoriTasks", () => {
const openApiKeyPropName = "OPENAI_API_KEY";
Expand All @@ -73,75 +64,16 @@ describe("generateDandoriTasks", () => {
});

describe(`without ${openApiKeyPropName} environment variable`, () => {
describe("with valid .env file", () => {
describe("no envFilePath argument", () => {
const apiKey = "123";
const envFileName = ".env";
const envFilePath = `./dir/.env`;

beforeAll(async () => {
await writeFile(envFileName, `${openApiKeyPropName}=${apiKey}`);
});

afterAll(async () => {
await rm(envFileName);
});

beforeEach(async () => {
await generateDandoriTasks("test");
});

it(`loaded ${openApiKeyPropName}`, () => {
expect(process.env[openApiKeyPropName]).toBe(apiKey);
});
});

describe("envFilePath argument", () => {
const apiKey = "456";
const envFileDir = "./dir";
const envFilePath = `./${envFileDir}/.env`;

beforeAll(async () => {
await mkdir(envFileDir);
await writeFile(envFilePath, `${openApiKeyPropName}=${apiKey}`);
});

afterAll(async () => {
await rm(envFilePath);
await rmdir(envFileDir);
});

beforeEach(async () => {
await generateDandoriTasks("test", {
envFilePath,
});
});

it(`loaded ${openApiKeyPropName}`, () => {
expect(process.env[openApiKeyPropName]).toBe(apiKey);
});
beforeEach(async () => {
await generateDandoriTasks("test", {
envFilePath,
});
});

describe("without valid .env file", () => {
let resultPromise: Promise<unknown>;

beforeEach(() => {
resultPromise = generateDandoriTasks("test", {
envFilePath: "./nodir/.env",
});
});

it("throw Error", async () => {
await expect(resultPromise).rejects.toThrowError();
});

it("called error log", async () => {
try {
await resultPromise;
} catch (e) {
expect(logger.error).toBeCalled();
}
});
it("call loadEnvFile with envFilePath argument", () => {
expect(loadEnvFileMock).toBeCalledWith(envFilePath);
});
});

Expand Down
15 changes: 2 additions & 13 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { configDotenv } from "dotenv";
import OpenAI from "openai";
import {
generateDandoriFilePath,
logger,
runPromisesSequentially,
} from "@dandori/libs";
import { loadEnvFile, logger, runPromisesSequentially } from "@dandori/libs";
import { ChatCompletionMessage } from "openai/resources";

export type ChatGPTFunctionCallModel = "gpt-3.5-turbo-0613" | "gpt-4-0613";
Expand Down Expand Up @@ -129,13 +124,7 @@ export default async function generateDandoriTasks(
options?: GenerateDandoriTasksOptions,
): Promise<DandoriTask[]> {
if (!process.env.OPENAI_API_KEY) {
const loadEnvResult = configDotenv({
path: generateDandoriFilePath(options?.envFilePath ?? ".env"),
});
if (loadEnvResult.error) {
logger.error(loadEnvResult.error);
throw loadEnvResult.error;
}
loadEnvFile(options?.envFilePath);
}
const openai = new OpenAI();
const model: ChatGPTFunctionCallModel =
Expand Down
1 change: 1 addition & 0 deletions packages/libs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"author": "Hiroki Miyaji",
"license": "MIT",
"dependencies": {
"dotenv": "^16.3.1",
"pino": "^8.16.1",
"pino-pretty": "^10.2.3"
}
Expand Down
88 changes: 88 additions & 0 deletions packages/libs/src/__tests__/loadEnvFile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { loadEnvFile, logger } from "../index";
import {
describe,
it,
expect,
beforeAll,
afterAll,
beforeEach,
vi,
afterEach,
} from "vitest";
import { mkdir, rm, rmdir, writeFile } from "fs/promises";

describe("loadEnvFile", () => {
describe("with valid .env file", () => {
const openApiKeyPropName = "OPENAI_API_KEY";

afterEach(() => {
delete process.env[openApiKeyPropName];
});

describe("no filePath argument", () => {
const apiKey = "123";
const envFileName = ".env";

beforeAll(async () => {
await writeFile(envFileName, `${openApiKeyPropName}=${apiKey}`);
});

afterAll(async () => {
await rm(envFileName);
});

beforeEach(() => {
loadEnvFile();
});

it(`loaded ${openApiKeyPropName}`, () => {
expect(process.env[openApiKeyPropName]).toBe(apiKey);
});
});

describe("filePath argument", () => {
const apiKey = "456";
const envFileDir = "./dir";
const envFilePath = `./${envFileDir}/.env`;

beforeAll(async () => {
await mkdir(envFileDir);
await writeFile(envFilePath, `${openApiKeyPropName}=${apiKey}`);
});

afterAll(async () => {
await rm(envFilePath);
await rmdir(envFileDir);
});

beforeEach(() => {
loadEnvFile(envFilePath);
});

it(`loaded ${openApiKeyPropName}`, () => {
expect(process.env[openApiKeyPropName]).toBe(apiKey);
});
});
});

describe("without valid .env file", () => {
const mockErrorLog = vi.spyOn(logger, "error").mockImplementation(() => {});
const runErrorLoadEnvFile = () => loadEnvFile("./nodir/.env");

afterEach(() => {
vi.clearAllMocks();
});

it("throw Error", async () => {
expect(runErrorLoadEnvFile).toThrowError();
});

it("called error log", async () => {
try {
runErrorLoadEnvFile();
} catch (e) {
expect(mockErrorLog).toBeCalled();
}
});
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { generateDandoriFilePath } from "../index";
import { loadFile } from "../index";
import { describe, it, expect } from "vitest";

describe("generateDandoriFilePath", () => {
describe("loadFile", () => {
describe("without arguments", () => {
it("returns cwd", () => {
expect(generateDandoriFilePath()).toBe(process.cwd());
expect(loadFile()).toBe(process.cwd());
});
});

Expand All @@ -13,9 +13,7 @@ describe("generateDandoriFilePath", () => {
const absoluteFilePath = "/path/to/file";

it("returns arguments path", () => {
expect(generateDandoriFilePath(absoluteFilePath)).toBe(
absoluteFilePath,
);
expect(loadFile(absoluteFilePath)).toBe(absoluteFilePath);
});
});

Expand All @@ -24,9 +22,7 @@ describe("generateDandoriFilePath", () => {
const relativeFilePath = `.${basePath}`;

it("returns arguments path with cwd", () => {
expect(generateDandoriFilePath(relativeFilePath)).toBe(
`${process.cwd()}${basePath}`,
);
expect(loadFile(relativeFilePath)).toBe(`${process.cwd()}${basePath}`);
});
});
});
Expand Down
3 changes: 2 additions & 1 deletion packages/libs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./generateDandoriFilePath";
export * from "./loadFile";
export * from "./loadEnvFile";
export * from "./logger";
export * from "./runPromisesSequentially";
13 changes: 13 additions & 0 deletions packages/libs/src/loadEnvFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { configDotenv } from "dotenv";
import { loadFile } from "./loadFile";
import { logger } from "./logger";

export function loadEnvFile(filePath: string = ".env"): void {
const loadEnvResult = configDotenv({
path: loadFile(filePath),
});
if (loadEnvResult.error) {
logger.error(loadEnvResult.error);
throw loadEnvResult.error;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "path";

export function generateDandoriFilePath(filePath?: string): string {
export function loadFile(filePath?: string): string {
const cwd = process.cwd();
if (!filePath) {
return cwd;
Expand Down
Loading

0 comments on commit 7405dab

Please sign in to comment.