Skip to content

Commit 8ed8cd0

Browse files
committed
feat: add all kinds of loaders
1 parent 47831b4 commit 8ed8cd0

22 files changed

Lines changed: 1708 additions & 96 deletions

bun.lock

Lines changed: 114 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,29 @@
6060
},
6161
"license": "MIT",
6262
"dependencies": {
63+
"@dotenvx/dotenvx": "^1.48.3",
6364
"@iarna/toml": "^2.2.5",
65+
"@types/cson": "^7.20.3",
66+
"@types/hjson": "^2.4.6",
67+
"@types/ini": "^4.1.1",
6468
"@types/js-yaml": "^4.0.9",
69+
"@types/papaparse": "^5.3.16",
6570
"ansis": "^4.1.0",
6671
"commander": "^14.0.0",
72+
"cson": "^8.4.0",
6773
"dotenv": "^17.2.0",
74+
"fast-xml-parser": "^5.2.5",
6875
"handlebars": "^4.7.8",
76+
"hjson": "^3.2.2",
77+
"ini": "^5.0.0",
6978
"jiti": "^2.4.2",
7079
"js-yaml": "^4.1.0",
80+
"json5": "^2.2.3",
81+
"jsonc-parser": "^3.3.1",
7182
"mustache": "^4.2.0",
7283
"nunjucks": "^3.2.4",
84+
"papaparse": "^5.5.3",
85+
"properties-file": "^3.5.13",
7386
"tsx": "^4.20.3",
7487
"zod": "^4.0.5"
7588
},

src/cli-helpers/builder.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
} from "../types";
99
import {commandRunner} from "./runner";
1010
import {pretty} from "../utils/pretty";
11+
import {zodIssuesToErrors} from "../utils/helpers.ts";
1112

1213
export function buildDynamicCommands(cli: Command, config: AxogenConfig): void {
1314
if (!config.commands) return;
@@ -83,26 +84,7 @@ function buildSchemaCommand(
8384
});
8485
} catch (error) {
8586
if (error instanceof z.ZodError) {
86-
const validationErrors = error.issues.map((issue) => {
87-
const field = issue.path.join(".");
88-
89-
// Determine error type
90-
let type: "missing" | "invalid" | "type" = "invalid";
91-
if (
92-
issue.code === "invalid_type" &&
93-
issue.input === undefined
94-
) {
95-
type = "missing";
96-
} else if (issue.code === "invalid_type") {
97-
type = "type";
98-
}
99-
100-
return {
101-
field,
102-
message: issue.message,
103-
type,
104-
};
105-
});
87+
const validationErrors = zodIssuesToErrors(error.issues);
10688

10789
pretty.validation.errorGroup(
10890
"Command validation failed",

src/core/config.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {type AxogenConfig, type ConfigInput, normalizeConfig} from "../types";
55
import {axogenConfigSchema} from "../types";
66
import {createJiti} from "jiti";
77
import {pretty} from "../utils/pretty";
8+
import {zodIssuesToErrors} from "../utils/helpers.ts";
89

910
export class ConfigLoader {
1011
/** Load configuration from a file */
@@ -31,27 +32,7 @@ export class ConfigLoader {
3132
return axogenConfigSchema.parse(config);
3233
} catch (error) {
3334
if (error instanceof z.ZodError) {
34-
const validationErrors = error.issues.map((issue) => {
35-
const field =
36-
issue.path.length > 0 ? issue.path.join(".") : "root";
37-
38-
// Determine error type
39-
let type: "missing" | "invalid" | "type" = "invalid";
40-
if (
41-
issue.code === "invalid_type" &&
42-
issue.input === undefined
43-
) {
44-
type = "missing";
45-
} else if (issue.code === "invalid_type") {
46-
type = "type";
47-
}
48-
49-
return {
50-
field,
51-
message: issue.message,
52-
type,
53-
};
54-
});
35+
const validationErrors = zodIssuesToErrors(error.issues);
5536

5637
pretty.validation.errorGroup(
5738
`Configuration validation failed in ${pretty.text.accent(resolvedPath)}`,

src/env/typed.ts

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {EnvSchema, ParsedEnv, EnvConfig} from "../types";
33
import {config} from "dotenv";
44
import {pretty} from "../utils/pretty";
55
import {isGitIgnored} from "../git/ignore-checker.ts";
6+
import {zodIssuesToErrors} from "../utils/helpers.ts";
67

78
config({
89
path: ".env.axogen",
@@ -26,46 +27,7 @@ function handleValidationError(
2627
config: Required<EnvConfig>
2728
): void {
2829
if (!config.silent) {
29-
const validationErrors = error.issues.map((issue) => {
30-
const field = issue.path.join(".");
31-
32-
// Determine error type based on Zod error codes
33-
let type: "missing" | "invalid" | "type" = "invalid";
34-
if (issue.code === "invalid_type" && issue.input === undefined) {
35-
type = "missing";
36-
} else if (issue.code === "invalid_type") {
37-
type = "type";
38-
}
39-
40-
// Create user-friendly error messages
41-
let message = issue.message;
42-
if (issue.code === "invalid_type") {
43-
const invalidTypeIssue = issue as any;
44-
if (
45-
invalidTypeIssue.expected &&
46-
invalidTypeIssue.received &&
47-
type === "type"
48-
) {
49-
message = `expected ${invalidTypeIssue.expected}, got ${invalidTypeIssue.received}`;
50-
}
51-
} else if (issue.code === "too_small") {
52-
const tooSmallIssue = issue as any;
53-
if (tooSmallIssue.minimum !== undefined) {
54-
message = `minimum value: ${tooSmallIssue.minimum}`;
55-
}
56-
} else if (issue.code === "too_big") {
57-
const tooBigIssue = issue as any;
58-
if (tooBigIssue.maximum !== undefined) {
59-
message = `maximum value: ${tooBigIssue.maximum}`;
60-
}
61-
}
62-
63-
return {
64-
field,
65-
message,
66-
type,
67-
};
68-
});
30+
const validationErrors = zodIssuesToErrors(error.issues);
6931

7032
pretty.validation.errorGroup(
7133
"Environment variable validation failed",

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,8 @@ export {loadEnv, createTypedEnv} from "./env/typed";
7272
// Export unsafe
7373
export {unsafe} from "./utils/secrets";
7474

75+
// Re-export loaders for convenience
76+
export * from "./loaders";
77+
7578
// Re-export Zod for convenience (users don't need to install it separately if they only use basic features)
7679
export {z} from "zod";

src/loaders/cson.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import CSON from "cson";
2+
import * as fs from "node:fs";
3+
4+
export function parseCsonFile(filePath: string) {
5+
try {
6+
const fileContent = fs.readFileSync(filePath, "utf-8");
7+
return CSON.parse(fileContent);
8+
} catch (error) {
9+
throw new Error(
10+
`Failed to parse CSON file at ${filePath}: ${
11+
error instanceof Error ? error.message : String(error)
12+
}`
13+
);
14+
}
15+
}

src/loaders/csv.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Papa from "papaparse";
2+
import * as fs from "node:fs";
3+
4+
export function parseCsvFile(filePath: string): any[] {
5+
try {
6+
const fileContent = fs.readFileSync(filePath, "utf8");
7+
8+
// Remove BOM if present
9+
const cleanContent = fileContent.replace(/^\uFEFF/, "");
10+
11+
const parseConfig: Papa.ParseConfig = {
12+
header: true,
13+
skipEmptyLines: true,
14+
fastMode: false, // Use slower but more accurate parsing
15+
comments: false, // Don't treat any lines as comments
16+
};
17+
18+
const result = Papa.parse(cleanContent, parseConfig);
19+
20+
if (result.errors && result.errors.length > 0) {
21+
// Filter out empty row errors which are common and acceptable
22+
const significantErrors = result.errors.filter(
23+
(error: any) =>
24+
!error.message.includes("Too few fields") &&
25+
!error.message.includes("Empty row") &&
26+
error.type !== "Quotes"
27+
);
28+
if (significantErrors.length > 0) {
29+
throw new Error(
30+
`CSV parsing errors: ${significantErrors.map((e: any) => e.message).join(", ")}`
31+
);
32+
}
33+
}
34+
35+
return result.data as any[];
36+
} catch (error) {
37+
throw new Error(
38+
`Failed to parse CSV file at ${filePath}: ${
39+
error instanceof Error ? error.message : String(error)
40+
}`
41+
);
42+
}
43+
}

src/loaders/env.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as fs from "node:fs";
2+
import {parse} from "@dotenvx/dotenvx";
3+
4+
export function parseEnvFile(filePath: string) {
5+
try {
6+
const fileContent = fs.readFileSync(filePath, "utf-8");
7+
return parse(fileContent);
8+
} catch (error) {
9+
throw new Error(
10+
`Failed to parse ENV file at ${filePath}: ${
11+
error instanceof Error ? error.message : String(error)
12+
}`
13+
);
14+
}
15+
}

src/loaders/hjson.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Hjson from "hjson";
2+
import * as fs from "node:fs";
3+
4+
export function parseHjsonFile(filePath: string) {
5+
try {
6+
const fileContent = fs.readFileSync(filePath, "utf-8");
7+
return Hjson.parse(fileContent);
8+
} catch (error) {
9+
throw new Error(
10+
`Failed to parse HJSON file at ${filePath}: ${
11+
error instanceof Error ? error.message : String(error)
12+
}`
13+
);
14+
}
15+
}

0 commit comments

Comments
 (0)