-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip(draft): progress on new system * test(unit): add tests * refactor: apply new config sys * style(biome): fix biome conflict * fix: fix stderr empty when non-zero-exit * feat(config): add write module * test: update and add cases * refactor: improve cancel util * feat: finished config prompt, refactor, fix bugs & test * fix(config): return default config if custom hasn't been created yet
- Loading branch information
Showing
18 changed files
with
378 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test" | ||
import { mkdir, rm } from "node:fs/promises" | ||
import { join } from "node:path" | ||
import { config, loadConfig } from "~/lib/config" | ||
import { writeDefaultConfig } from "~/lib/config/write" | ||
|
||
const WRITE_TEST_PATH = ".temp/conmmit" | ||
|
||
beforeAll(() => config.init()) | ||
beforeEach(async () => await mkdir(WRITE_TEST_PATH, { recursive: true })) | ||
afterEach(async () => await rm(WRITE_TEST_PATH, { recursive: true, force: true })) | ||
|
||
describe("Config Loading", () => { | ||
test("should load default config", async () => { | ||
const config = await loadConfig() | ||
|
||
expect(config).toBeDefined() | ||
expect(config.commit_types).toBeArray() | ||
expect(config.commit_types.length).toBeGreaterThan(0) | ||
}) | ||
|
||
test("should validate commit type structure", async () => { | ||
const config = await loadConfig() | ||
const firstType = config.commit_types[0] | ||
|
||
expect(firstType).toHaveProperty("name") | ||
expect(firstType).toHaveProperty("description") | ||
expect(firstType).toHaveProperty("example_scopes") | ||
expect(firstType.example_scopes).toBeArray() | ||
}) | ||
|
||
test("should throw on invalid config structure", async () => { | ||
// this is enough thanks to abortPipeEarly on validation fn | ||
const invalidConfig = ` | ||
[[commit_types]] | ||
name = "" | ||
` | ||
const configPath = join(WRITE_TEST_PATH, "invalid-config.toml") | ||
const configFile = Bun.file(configPath) | ||
await configFile.write(invalidConfig) | ||
|
||
expect(loadConfig(configPath)).rejects.toThrow() | ||
}) | ||
|
||
test("should load config from custom path", async () => { | ||
const customConfig = ` | ||
[[commit_types]] | ||
name = "custom" | ||
description = "Custom type" | ||
example_scopes = ["test"] | ||
` | ||
const configPath = join(WRITE_TEST_PATH, "custom-config.toml") | ||
const configFile = Bun.file(configPath) | ||
await configFile.write(customConfig) | ||
const { | ||
commit_types: [commit_type], | ||
} = await loadConfig(configPath) | ||
|
||
expect(commit_type).toHaveProperty("name", "custom") | ||
expect(commit_type).toHaveProperty("description", "Custom type") | ||
expect(commit_type).toHaveProperty("example_scopes", ["test"]) | ||
}) | ||
}) | ||
|
||
describe("Config Writing", () => { | ||
test("should copy default config to user's filesystem", async () => { | ||
const configPath = join(WRITE_TEST_PATH, "config.toml") | ||
const userConfigFile = Bun.file(configPath) | ||
|
||
const defaultConfig = await loadConfig() | ||
|
||
await writeDefaultConfig({ customPath: configPath }) | ||
const userConfig = await loadConfig(configPath) | ||
|
||
expect(await userConfigFile.exists()).toBeTrue() | ||
expect(userConfig).toStrictEqual(defaultConfig) | ||
}) | ||
|
||
test("should not overwrite existing config without override", async () => { | ||
const customConfig = ` | ||
[[commit_types]] | ||
name = "custom" | ||
description = "Custom type" | ||
example_scopes = ["test"] | ||
` | ||
const configPath = join(WRITE_TEST_PATH, "config.toml") | ||
const configFile = Bun.file(configPath) | ||
await configFile.write(customConfig) | ||
|
||
const userConfigOriginal = await loadConfig(configPath) | ||
expect(writeDefaultConfig({ customPath: configPath })).rejects.toThrow() | ||
const userConfigAfterAttempt = await loadConfig(configPath) | ||
expect(userConfigAfterAttempt).toStrictEqual(userConfigOriginal) | ||
}) | ||
|
||
test("should overwrite existing config with override", async () => { | ||
const customConfig = ` | ||
[[commit_types]] | ||
name = "custom" | ||
description = "Custom type" | ||
example_scopes = ["test"] | ||
` | ||
const configPath = join(WRITE_TEST_PATH, "config.toml") | ||
const configFile = Bun.file(configPath) | ||
await configFile.write(customConfig) | ||
|
||
const defaultConfig = await loadConfig() | ||
await writeDefaultConfig({ customPath: configPath, override: true }) | ||
const userConfig = await loadConfig(configPath) | ||
expect(userConfig).toStrictEqual(defaultConfig) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
"dependencies": { | ||
"@clack/prompts": "^0.9.1", | ||
"picocolors": "^1.1.1", | ||
"valibot": "^1.0.0-beta.14", | ||
}, | ||
"devDependencies": { | ||
"@biomejs/biome": "^1.9.4", | ||
|
@@ -227,6 +228,8 @@ | |
|
||
"undici-types": ["[email protected]", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], | ||
|
||
"valibot": ["[email protected]", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-tLyV2rE5QL6U29MFy3xt4AqMrn+/HErcp2ZThASnQvPMwfSozjV1uBGKIGiegtZIGjinJqn0SlBdannf18wENA=="], | ||
|
||
"which": ["[email protected]", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], | ||
|
||
"yallist": ["[email protected]", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# COMMIT TYPES | ||
[[commit_types]] | ||
name = "feat" | ||
description = "A new feature" | ||
example_scopes = ["api", "ui", "auth", "db"] | ||
|
||
[[commit_types]] | ||
name = "fix" | ||
description = "A bug fix" | ||
example_scopes = ["memory", "crash", "data", "performance"] | ||
|
||
[[commit_types]] | ||
name = "wip" | ||
description = "Work in progress" | ||
example_scopes = ["prototype", "experiment", "draft", "concept"] | ||
|
||
[[commit_types]] | ||
name = "docs" | ||
description = "Documentation only changes" | ||
example_scopes = ["guide", "setup", "reference", "faq"] | ||
|
||
[[commit_types]] | ||
name = "style" | ||
description = "Code style changes (white-space, formatting, semi-colons, etc)" | ||
example_scopes = ["format", "lint", "typo"] | ||
|
||
[[commit_types]] | ||
name = "refactor" | ||
description = "A code change that neither fixes a bug nor adds a feature" | ||
example_scopes = ["cleanup", "split", "interface"] | ||
|
||
[[commit_types]] | ||
name = "perf" | ||
description = "A code change that improves performance" | ||
example_scopes = ["query", "cache", "workers"] | ||
|
||
[[commit_types]] | ||
name = "test" | ||
description = "Adding missing tests or correcting existing ones" | ||
example_scopes = ["unit", "integration", "e2e", "performance"] | ||
|
||
[[commit_types]] | ||
name = "build" | ||
description = "Changes that affect the build system or external deps" | ||
example_scopes = ["webpack", "babel", "npm", "gradle"] | ||
|
||
[[commit_types]] | ||
name = "ci" | ||
description = "Changes to the CI configuration files and scripts" | ||
example_scopes = ["actions", "gitlab", "jenkins", "circleci"] | ||
|
||
[[commit_types]] | ||
name = "chore" | ||
description = "Other changes that don't modify src or test files" | ||
example_scopes = ["deps", "tooling", "workflow", "structure"] | ||
|
||
[[commit_types]] | ||
name = "revert" | ||
description = "Reverts a previous commit" | ||
example_scopes = ["rollback"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import internals from "./internals" | ||
import { loadConfig } from "./load" | ||
import type { Config } from "./schema" | ||
|
||
const config = (() => { | ||
let cache: Config | undefined | ||
|
||
return { | ||
init: async (customPath?: string) => { | ||
if (cache) return cache | ||
cache = await loadConfig(customPath || internals.customConfigPath) | ||
return cache | ||
}, | ||
get: () => { | ||
if (!cache) throw new Error("Config not initialized.") | ||
return { ...cache, internals } | ||
}, | ||
} | ||
})() | ||
|
||
export { config, type Config, loadConfig } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { homedir } from "node:os" | ||
import { join } from "node:path" | ||
|
||
export default { | ||
lineMaxLength: 100, | ||
lineMinLength: 3, | ||
customConfigPath: join(homedir(), ".conmmit/config.toml"), | ||
} as const |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { TOML } from "bun" | ||
import { safeParse } from "valibot" | ||
import defaultConfig from "./default.toml" | ||
import { type Config, ConfigSchema } from "./schema" | ||
|
||
export async function loadConfig(customPath?: string): Promise<Config> { | ||
try { | ||
if (!customPath) return validateConfig(defaultConfig) | ||
const configFile = Bun.file(customPath) | ||
if (!(await configFile.exists())) return validateConfig(defaultConfig) | ||
const parsedConfig = TOML.parse(await configFile.text()) | ||
return validateConfig(parsedConfig) | ||
} catch (error) { | ||
throw new Error(`${error instanceof Error ? error.message : "Unknown error"}`) | ||
} | ||
} | ||
|
||
function validateConfig(config: unknown): Config { | ||
const result = safeParse(ConfigSchema, config, { abortPipeEarly: true }) | ||
|
||
if (!result.success) { | ||
throw new Error(`Invalid config: ${result.issues[0]?.message}`) | ||
} | ||
|
||
return result.output | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { type InferOutput, array, maxLength, minLength, object, pipe, string } from "valibot" | ||
|
||
export const CommitTypeSchema = object({ | ||
name: pipe( | ||
string(), | ||
minLength(1, "Type name cannot be empty"), | ||
maxLength(12, "Type name cannot exceed 12 characters") | ||
), | ||
description: pipe( | ||
string(), | ||
minLength(1, "Description cannot be empty"), | ||
maxLength(90, "Description cannot exceed 90 characters") | ||
), | ||
example_scopes: pipe( | ||
array( | ||
pipe( | ||
string(), | ||
minLength(1, "Scope cannot be empty"), | ||
maxLength(12, "Scope cannot exceed 12 characters") | ||
) | ||
), | ||
minLength(1, "At least 1 example scope is required"), | ||
maxLength(5, "At most 5 example scopes are allowed") | ||
), | ||
}) | ||
|
||
export const ConfigSchema = object({ | ||
commit_types: pipe(array(CommitTypeSchema), minLength(1, "At least 1 commit type is required")), | ||
}) | ||
|
||
export type CommitType = InferOutput<typeof CommitTypeSchema> | ||
export type Config = InferOutput<typeof ConfigSchema> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import internals from "./internals" | ||
|
||
export async function writeDefaultConfig({ | ||
customPath, | ||
override = false, | ||
}: { | ||
customPath?: string | ||
override?: boolean | ||
}) { | ||
try { | ||
const userConfigPath = customPath || internals.customConfigPath | ||
const userConfigFile = Bun.file(userConfigPath) | ||
|
||
if ((await userConfigFile.exists()) && !override) | ||
throw new Error(`Config file already exists at ${userConfigPath}`) | ||
|
||
const defaultConfigFile = Bun.file(new URL("./default.toml", import.meta.url).pathname) | ||
|
||
// not sure why BunFile.write is not working but Bun.write does... | ||
await Bun.write(userConfigPath, defaultConfigFile) | ||
} catch (error) { | ||
throw new Error( | ||
`Failed to write config: ${error instanceof Error ? error.message : "Unknown error"}` | ||
) | ||
} | ||
} |
Oops, something went wrong.