Skip to content

Commit 3e7830b

Browse files
authored
fix: support a base different from / in astro config (#92)
1 parent 01fe702 commit 3e7830b

File tree

15 files changed

+122
-16
lines changed

15 files changed

+122
-16
lines changed

packages/astro/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"dist"
1818
],
1919
"scripts": {
20-
"build": "node ./scripts/build.js"
20+
"build": "node ./scripts/build.js",
21+
"test": "vitest"
2122
},
2223
"dependencies": {
2324
"@astrojs/mdx": "^3.1.1",
@@ -55,7 +56,8 @@
5556
"esbuild-node-externals": "^1.13.1",
5657
"execa": "^9.2.0",
5758
"typescript": "^5.4.5",
58-
"vite-plugin-inspect": "0.8.4"
59+
"vite-plugin-inspect": "0.8.4",
60+
"vitest": "^1.6.0"
5961
},
6062
"peerDependencies": {
6163
"astro": "^4.10.2"

packages/astro/scripts/build.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
33
import { cp, rm } from 'node:fs/promises';
44
import { execa } from 'execa';
55
import esbuild from 'esbuild';
6+
import glob from 'fast-glob';
67
import { nodeExternalsPlugin } from 'esbuild-node-externals';
78

89
// clean dist
@@ -15,7 +16,7 @@ execa('tsc', ['--emitDeclarationOnly', '--project', './tsconfig.build.json'], {
1516
});
1617

1718
// build with esbuild
18-
esbuild.build({
19+
await esbuild.build({
1920
entryPoints: ['src/index.ts'],
2021
bundle: true,
2122
tsconfig: './tsconfig.build.json',
@@ -34,3 +35,6 @@ if (existsSync('./dist/default')) {
3435

3536
// copy default folder unmodified
3637
await cp('./src/default', './dist/default', { recursive: true });
38+
39+
// remove test files
40+
await glob('./dist/default/**/*.spec.ts').then((testFiles) => Promise.all(testFiles.map((testFile) => rm(testFile))));

packages/astro/src/default/components/Logo.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import fs from 'node:fs';
33
import path from 'node:path';
4+
import { joinPaths } from '../utils/url';
45
56
const LOGO_EXTENSIONS = ['svg', 'png', 'jpeg', 'jpg'];
67
@@ -16,7 +17,7 @@ function readLogoFile(logoPrefix: string) {
1617
const exists = fs.existsSync(path.join('public', logoFilename));
1718
1819
if (exists) {
19-
logo = `/${logoFilename}`;
20+
logo = joinPaths(import.meta.env.BASE_URL, logoFilename);
2021
break;
2122
}
2223
}

packages/astro/src/default/components/webcontainer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useAuth } from './setup.js';
33

44
import { TutorialStore } from '@tutorialkit/runtime';
55
import { auth, WebContainer } from '@webcontainer/api';
6+
import { joinPaths } from '../utils/url.js';
67

78
interface WebContainerContext {
89
useAuth: boolean;
@@ -24,7 +25,11 @@ if (!import.meta.env.SSR) {
2425
});
2526
}
2627

27-
export const tutorialStore = new TutorialStore({ webcontainer, useAuth });
28+
export const tutorialStore = new TutorialStore({
29+
webcontainer,
30+
useAuth,
31+
basePathname: joinPaths(import.meta.env.BASE_URL, '/'),
32+
});
2833

2934
export async function login() {
3035
auth.startAuthFlow({ popup: true });

packages/astro/src/default/layouts/Layout.astro

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
---
22
import { ViewTransitions } from 'astro:transitions';
3+
import { joinPaths } from '../utils/url';
34
45
interface Props {
56
title: string;
67
}
78
89
const { title } = Astro.props;
10+
const baseURL = import.meta.env.BASE_URL;
911
---
1012

1113
<!doctype html>
@@ -16,7 +18,7 @@ const { title } = Astro.props;
1618
<meta name="viewport" content="width=device-width" />
1719
<meta name="generator" content={Astro.generator} />
1820
<title>{title}</title>
19-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
21+
<link rel="icon" type="image/svg+xml" href={joinPaths(baseURL, '/favicon.svg')} />
2022
<link rel="preconnect" href="https://fonts.googleapis.com" />
2123
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
2224
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />

packages/astro/src/default/pages/index.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
---
22
import { getTutorial } from '../utils/content';
3+
import { joinPaths } from '../utils/url';
34
45
const tutorial = await getTutorial();
56
67
const part = tutorial.parts[tutorial.firstPartId!];
78
const chapter = part.chapters[part?.firstChapterId!];
89
const lesson = chapter.lessons[chapter?.firstLessonId!];
910
10-
const redirect = `/${part.slug}/${chapter.slug}/${lesson.slug}`;
11+
const redirect = joinPaths(import.meta.env.BASE_URL, `/${part.slug}/${chapter.slug}/${lesson.slug}`);
1112
---
1213

1314
<!doctype html>

packages/astro/src/default/utils/content.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getCollection } from 'astro:content';
1212
import glob from 'fast-glob';
1313
import path from 'node:path';
1414
import { logger } from './logger';
15+
import { joinPaths } from './url';
1516

1617
const CONTENT_DIR = path.join(process.cwd(), 'src/content/tutorial');
1718

@@ -226,6 +227,8 @@ export async function getTutorial(): Promise<Tutorial> {
226227
return 0;
227228
});
228229

230+
const baseURL = import.meta.env.BASE_URL;
231+
229232
// now we link all lessons together
230233
for (const [i, lesson] of lessons.entries()) {
231234
const prevLesson = i > 0 ? lessons.at(i - 1) : undefined;
@@ -248,7 +251,7 @@ export async function getTutorial(): Promise<Tutorial> {
248251

249252
lesson.prev = {
250253
title: prevLesson.data.title,
251-
href: `/${partSlug}/${chapterSlug}/${prevLesson.slug}`,
254+
href: joinPaths(baseURL, `/${partSlug}/${chapterSlug}/${prevLesson.slug}`),
252255
};
253256
}
254257

@@ -258,7 +261,7 @@ export async function getTutorial(): Promise<Tutorial> {
258261

259262
lesson.next = {
260263
title: nextLesson.data.title,
261-
href: `/${partSlug}/${chapterSlug}/${nextLesson.slug}`,
264+
href: joinPaths(baseURL, `/${partSlug}/${chapterSlug}/${nextLesson.slug}`),
262265
};
263266
}
264267
}

packages/astro/src/default/utils/nav.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Tutorial, NavList } from '@tutorialkit/types';
2+
import { joinPaths } from './url';
23

3-
export function generateNavigationList(tutorial: Tutorial): NavList {
4+
export function generateNavigationList(tutorial: Tutorial, baseURL: string): NavList {
45
return objectToSortedArray(tutorial.parts).map((part) => {
56
return {
67
id: part.id,
@@ -13,7 +14,7 @@ export function generateNavigationList(tutorial: Tutorial): NavList {
1314
return {
1415
id: lesson.id,
1516
title: lesson.data.title,
16-
href: `/${part.slug}/${chapter.slug}/${lesson.slug}`,
17+
href: joinPaths(baseURL, `/${part.slug}/${chapter.slug}/${lesson.slug}`),
1718
};
1819
}),
1920
};

packages/astro/src/default/utils/routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function generateStaticRoutes() {
2424
},
2525
props: {
2626
logoLink: tutorial.logoLink,
27-
navList: generateNavigationList(tutorial),
27+
navList: generateNavigationList(tutorial, import.meta.env.BASE_URL),
2828
title: `${part.data.title} / ${chapter.data.title} / ${lesson.data.title}`,
2929
lesson: lesson as Lesson<AstroComponentFactory>,
3030
},
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { it, describe, expect } from 'vitest';
2+
import { joinPaths } from './url';
3+
4+
describe('joinPaths', () => {
5+
it('should join paths', () => {
6+
expect(joinPaths('/a', 'b')).toBe('/a/b');
7+
expect(joinPaths('/a/', 'b')).toBe('/a/b');
8+
expect(joinPaths('/a', '/b')).toBe('/a/b');
9+
expect(joinPaths('/a/', '/b')).toBe('/a/b');
10+
expect(joinPaths('/', '/')).toBe('/');
11+
});
12+
13+
it('should join multiple paths', () => {
14+
expect(joinPaths('/a', 'b', '/c')).toBe('/a/b/c');
15+
expect(joinPaths('/a', '/b', 'c')).toBe('/a/b/c');
16+
expect(joinPaths('/a/', '/b', '/c')).toBe('/a/b/c');
17+
});
18+
19+
it('should join paths with empty strings', () => {
20+
expect(joinPaths('', 'b')).toBe('/b');
21+
expect(joinPaths('/a', '')).toBe('/a');
22+
expect(joinPaths('', '')).toBe('/');
23+
});
24+
25+
it('should join paths with empty strings in the middle', () => {
26+
expect(joinPaths('/a', '', 'b')).toBe('/a/b');
27+
expect(joinPaths('/a', '', '', 'b')).toBe('/a/b');
28+
});
29+
30+
it('should keep trailing slashes', () => {
31+
expect(joinPaths('/a/')).toBe('/a/');
32+
expect(joinPaths('/a/', 'b/')).toBe('/a/b/');
33+
expect(joinPaths('/a/', 'b/', '/c')).toBe('/a/b/c');
34+
expect(joinPaths('/a/', 'b/', '/c/')).toBe('/a/b/c/');
35+
});
36+
});

0 commit comments

Comments
 (0)