Skip to content

Commit c0e2b80

Browse files
authored
fix: allow relative paths for outDir and staticDir (#2914)
This PR consists of several commits, where the goal is to re-enable the test (fix for Windows), as well as allow relative paths for config options to address #2913. 1. Re-enables test and normalizes paths for Windows 2. Add unit test for `normalizeConfig` and the `root` option 3. Add unit test for `normalizeConfig` for `build.outDir` and `staticDir`, and allow the properties to be relative 4. Add JSDoc for fields in `FreshConfig` I can split these into separate PR's if preferred, but all code changes are very in nearly the same places. Closes #2913
1 parent d10ea5a commit c0e2b80

File tree

2 files changed

+143
-21
lines changed

2 files changed

+143
-21
lines changed

src/config.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
import * as path from "@std/path";
22

33
export interface FreshConfig {
4+
/**
5+
* The root directory of the Fresh project.
6+
*
7+
* Other paths, such as `build.outDir`, `staticDir`, and `fsRoutes()`
8+
* are resolved relative to this directory.
9+
* @default Deno.cwd()
10+
*/
411
root?: string;
512
build?: {
613
/**
714
* The directory to write generated files to when `dev.ts build` is run.
15+
*
816
* This can be an absolute path, a file URL or a relative path.
17+
* Relative paths are resolved against the `root` option.
18+
* @default "_fresh"
919
*/
1020
outDir?: string;
1121
};
1222
/**
1323
* Serve fresh from a base path instead of from the root.
1424
* "/foo/bar" -> http://localhost:8000/foo/bar
15-
* @default {undefined}
25+
* @default undefined
1626
*/
1727
basePath?: string;
28+
/**
29+
* The directory to serve static files from.
30+
*
31+
* This can be an absolute path, a file URL or a relative path.
32+
* Relative paths are resolved against the `root` option.
33+
* @default "static"
34+
*/
1835
staticDir?: string;
1936
}
2037

@@ -35,35 +52,47 @@ export interface ResolvedFreshConfig {
3552
}
3653

3754
export function parseRootPath(root: string, cwd: string): string {
38-
if (root.startsWith("file://")) {
39-
root = path.fromFileUrl(root);
40-
} else if (!path.isAbsolute(root)) {
41-
root = path.join(cwd, root);
55+
return parseDirPath(root, cwd, true);
56+
}
57+
58+
function parseDirPath(
59+
dirPath: string,
60+
root: string,
61+
fileToDir = false,
62+
): string {
63+
if (dirPath.startsWith("file://")) {
64+
dirPath = path.fromFileUrl(dirPath);
65+
} else if (!path.isAbsolute(dirPath)) {
66+
dirPath = path.join(root, dirPath);
67+
}
68+
69+
if (fileToDir) {
70+
const ext = path.extname(dirPath);
71+
if (
72+
ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" ||
73+
ext === ".mjs"
74+
) {
75+
dirPath = path.dirname(dirPath);
76+
}
4277
}
4378

44-
const ext = path.extname(root);
45-
if (
46-
ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" ||
47-
ext === ".mjs"
48-
) {
49-
root = path.dirname(root);
79+
if (Deno.build.os === "windows") {
80+
dirPath = dirPath.replaceAll("\\", "/");
5081
}
5182

52-
return root;
83+
return dirPath;
5384
}
5485

5586
export function normalizeConfig(options: FreshConfig): ResolvedFreshConfig {
56-
const root = options.root
57-
? parseRootPath(options.root, Deno.cwd())
58-
: Deno.cwd();
87+
const root = parseRootPath(options.root ?? ".", Deno.cwd());
5988

6089
return {
6190
root,
6291
build: {
63-
outDir: options.build?.outDir ?? path.join(root, "_fresh"),
92+
outDir: parseDirPath(options.build?.outDir ?? "_fresh", root),
6493
},
6594
basePath: options.basePath ?? "",
66-
staticDir: options.staticDir ?? path.join(root, "static"),
95+
staticDir: parseDirPath(options.staticDir ?? "static", root),
6796
mode: "production",
6897
};
6998
}

src/config_test.ts

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,108 @@
11
import { expect } from "@std/expect";
2-
import { parseRootPath } from "./config.ts";
2+
import { normalizeConfig, parseRootPath } from "./config.ts";
3+
import type { FreshConfig } from "./mod.ts";
34

4-
// FIXME: Windows
5-
Deno.test.ignore("parseRootPath", () => {
6-
const cwd = Deno.cwd();
5+
Deno.test("parseRootPath", () => {
6+
const cwd = Deno.cwd().replaceAll("\\", "/");
7+
8+
// File paths
79
expect(parseRootPath("file:///foo/bar", cwd)).toEqual("/foo/bar");
810
expect(parseRootPath("file:///foo/bar.ts", cwd)).toEqual("/foo");
11+
if (Deno.build.os === "windows") {
12+
expect(parseRootPath("file:///C:/foo/bar", cwd)).toEqual("C:/foo/bar");
13+
expect(parseRootPath("file:///C:/foo/bar.ts", cwd)).toEqual("C:/foo");
14+
}
15+
16+
// Relative paths
17+
expect(parseRootPath("./foo/bar", cwd)).toEqual(`${cwd}/foo/bar`);
18+
expect(parseRootPath("./foo/bar.ts", cwd)).toEqual(`${cwd}/foo`);
19+
20+
// Absolute paths
921
expect(parseRootPath("/foo/bar", cwd)).toEqual("/foo/bar");
1022
expect(parseRootPath("/foo/bar.ts", cwd)).toEqual("/foo");
1123
expect(parseRootPath("/foo/bar.tsx", cwd)).toEqual("/foo");
1224
expect(parseRootPath("/foo/bar.js", cwd)).toEqual("/foo");
1325
expect(parseRootPath("/foo/bar.jsx", cwd)).toEqual("/foo");
1426
expect(parseRootPath("/foo/bar.mjs", cwd)).toEqual("/foo");
27+
if (Deno.build.os === "windows") {
28+
expect(parseRootPath("C:/foo/bar", cwd)).toEqual("C:/foo/bar");
29+
expect(parseRootPath("C:/foo/bar.ts", cwd)).toEqual("C:/foo");
30+
}
31+
});
32+
33+
Deno.test("normalizeConfig - root", () => {
34+
const cwd = Deno.cwd().replaceAll("\\", "/");
35+
const configRoot = (root?: string) => normalizeConfig({ root }).root;
36+
37+
expect(configRoot()).toEqual(cwd);
38+
expect(configRoot("/foo/bar")).toEqual("/foo/bar");
39+
expect(configRoot("/foo/bar.ts")).toEqual("/foo");
40+
expect(configRoot("file:///foo/bar")).toEqual("/foo/bar");
41+
expect(configRoot("./foo/bar")).toEqual(`${cwd}/foo/bar`);
42+
expect(configRoot("./foo/bar.ts")).toEqual(`${cwd}/foo`);
43+
44+
if (Deno.build.os === "windows") {
45+
expect(configRoot("C:/foo/bar.ts")).toEqual("C:/foo");
46+
expect(configRoot("file:///C:/foo/bar")).toEqual("C:/foo/bar");
47+
}
48+
});
49+
50+
Deno.test("normalizeConfig - build.outDir", () => {
51+
const cwd = Deno.cwd().replaceAll("\\", "/");
52+
const outDir = (options: FreshConfig) =>
53+
normalizeConfig(options).build.outDir;
54+
55+
// Default outDir
56+
expect(outDir({ root: "./src" })).toEqual(`${cwd}/src/_fresh`);
57+
expect(outDir({ root: "/src" })).toEqual("/src/_fresh");
58+
expect(outDir({ root: "file:///src" })).toEqual("/src/_fresh");
59+
60+
// Relative outDir
61+
expect(outDir({ root: "/src", build: { outDir: "dist" } })).toEqual(
62+
"/src/dist",
63+
);
64+
expect(outDir({ root: "/src", build: { outDir: "./dist" } })).toEqual(
65+
"/src/dist",
66+
);
67+
68+
// Absolute outDir
69+
expect(outDir({ root: "/src", build: { outDir: "/dist" } })).toEqual(
70+
"/dist",
71+
);
72+
expect(outDir({ root: "/src", build: { outDir: "/dist/fresh" } })).toEqual(
73+
"/dist/fresh",
74+
);
75+
expect(outDir({ root: "/src", build: { outDir: "file:///dist" } })).toEqual(
76+
"/dist",
77+
);
78+
});
79+
80+
Deno.test("normalizeConfig - staticDir", () => {
81+
const cwd = Deno.cwd().replaceAll("\\", "/");
82+
const staticDir = (options: FreshConfig) =>
83+
normalizeConfig(options).staticDir;
84+
85+
// Default staticDir
86+
expect(staticDir({ root: "./src" })).toEqual(`${cwd}/src/static`);
87+
expect(staticDir({ root: "/src" })).toEqual("/src/static");
88+
expect(staticDir({ root: "file:///src" })).toEqual("/src/static");
89+
90+
// Relative staticDir
91+
expect(staticDir({ root: "/src", staticDir: "public" })).toEqual(
92+
"/src/public",
93+
);
94+
expect(staticDir({ root: "/src", staticDir: "./public" })).toEqual(
95+
"/src/public",
96+
);
97+
98+
// Absolute staticDir
99+
expect(staticDir({ root: "/src", staticDir: "/public" })).toEqual(
100+
"/public",
101+
);
102+
expect(staticDir({ root: "/src", staticDir: "/public/assets" })).toEqual(
103+
"/public/assets",
104+
);
105+
expect(staticDir({ root: "/src", staticDir: "file:///public" })).toEqual(
106+
"/public",
107+
);
15108
});

0 commit comments

Comments
 (0)