@abreen/tada 1.11.2 → 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +65 -64
  2. package/bin/tada.ts +61 -66
  3. package/build/build-manifest.ts +14 -3
  4. package/build/bundle.ts +0 -1
  5. package/build/config-files.ts +183 -0
  6. package/build/config-loader.ts +159 -0
  7. package/build/copy.ts +0 -83
  8. package/build/generate-content-assets.ts +75 -160
  9. package/build/globals.ts +48 -89
  10. package/build/log.ts +27 -18
  11. package/build/pagefind.ts +33 -26
  12. package/build/pipeline.ts +98 -79
  13. package/build/reachability.ts +51 -248
  14. package/build/serve.ts +35 -6
  15. package/build/site-variables.ts +20 -11
  16. package/build/source-model.ts +400 -0
  17. package/build/{watch/records.ts → source-records.ts} +111 -49
  18. package/build/template-globals.ts +91 -0
  19. package/build/templates.ts +50 -40
  20. package/build/test-helpers.ts +15 -0
  21. package/build/toc-plugin.ts +5 -1
  22. package/build/types.d.ts +9 -1
  23. package/build/util.ts +8 -0
  24. package/build/utils/code.ts +6 -16
  25. package/build/utils/content-files.ts +57 -126
  26. package/build/utils/final-html.ts +239 -0
  27. package/build/utils/markdown.ts +237 -17
  28. package/build/utils/paths.ts +2 -1
  29. package/build/utils/render.ts +108 -99
  30. package/build/validate-config-links.ts +91 -14
  31. package/build/watch/assets.ts +0 -2
  32. package/build/watch/build-full.ts +14 -15
  33. package/build/watch/build-incremental.ts +15 -8
  34. package/build/watch/compiler-types.d.ts +6 -1
  35. package/build/watch/compiler.ts +27 -15
  36. package/build/watch/index.ts +2 -7
  37. package/build/watch/planner.ts +32 -16
  38. package/build/watch/reload.ts +4 -0
  39. package/build/watch/runtime.ts +29 -45
  40. package/build/watch/snapshot.ts +50 -402
  41. package/build/watch/validation.ts +6 -3
  42. package/build/watch-reachability-state.ts +16 -23
  43. package/build/watch-reload-client.ts +11 -4
  44. package/init/content/index.md +1 -1
  45. package/init/content/labs/02/index.md +61 -0
  46. package/init/content/labs/02/selection_sort.py +23 -0
  47. package/init/content/labs/index.md +4 -2
  48. package/init/content/lectures/01/index.md +1 -1
  49. package/init/content/markdown.md +1 -1
  50. package/package.json +3 -4
  51. package/src/code/index.ts +46 -18
  52. package/src/code.scss +2 -1
  53. package/src/globals.ts +47 -0
  54. package/src/index.ts +1 -0
  55. package/src/navigate/index.ts +2 -5
  56. package/src/navigate/lifecycle.ts +3 -1
  57. package/src/navigate/runtime.ts +6 -5
  58. package/src/page-update/index.ts +20 -16
  59. package/src/search/index.ts +53 -25
  60. package/src/search/style.scss +1 -1
  61. package/src/slides/index.ts +467 -0
  62. package/src/slides/style.scss +104 -0
  63. package/src/style.scss +8 -3
  64. package/src/timezone/index.ts +16 -20
  65. package/src/top/index.ts +2 -4
  66. package/src/trace/index.ts +91 -7
  67. package/src/trace/style.scss +1 -1
  68. package/src/util.ts +6 -2
  69. package/templates/_author.html +2 -2
  70. package/templates/_heading.html +1 -1
  71. package/templates/_nav.html +2 -2
  72. package/templates/_theme.scss +3 -3
  73. package/templates/_top.html +14 -14
  74. package/templates/default.html +8 -0
  75. package/types/dev.d.ts +1 -1
  76. package/types/site-variables.d.ts +4 -5
  77. package/watch/fs-commit.ts +63 -10
  78. package/build/apply-base-path-plugin.ts +0 -186
  79. package/build/content-watch.ts +0 -104
  80. package/build/validate-internal-links-plugin.ts +0 -207
@@ -0,0 +1,239 @@
1
+ import path from 'path';
2
+ import { JSDOM } from 'jsdom';
3
+ import { getExtensionToShikiLanguage } from '../site-variables';
4
+ import { createApplyBasePath, normalizeOutputPath } from './paths';
5
+ import { isInternalLink } from './link';
6
+ import type {
7
+ HtmlOutputAnalysis,
8
+ RenderDependencyCollector,
9
+ SiteVariables,
10
+ } from '../types';
11
+
12
+ interface FinalizeHtmlPageOptions {
13
+ filePath: string;
14
+ html: string;
15
+ siteVariables: SiteVariables;
16
+ sourceUrlPath: string;
17
+ validInternalTargets: Set<string>;
18
+ literateJavaOutputPaths?: Set<string>;
19
+ dependencyCollector?: RenderDependencyCollector;
20
+ }
21
+
22
+ interface FinalizedHtmlPage {
23
+ html: string;
24
+ analysis: HtmlOutputAnalysis;
25
+ }
26
+
27
+ function splitHref(href: string): { pathname: string; suffix: string } {
28
+ const match = href.match(/^([^?#]*)(.*)$/);
29
+ return { pathname: match ? match[1] : href, suffix: match ? match[2] : '' };
30
+ }
31
+
32
+ function resolvePathname(
33
+ sourceUrlPath: string,
34
+ pathname: string,
35
+ ): string | null {
36
+ if (!pathname) {
37
+ return null;
38
+ }
39
+
40
+ const sourceDir = path.posix.dirname(sourceUrlPath);
41
+ const resolved = pathname.startsWith('/')
42
+ ? normalizeOutputPath(pathname)
43
+ : normalizeOutputPath(path.posix.join(sourceDir, pathname));
44
+
45
+ try {
46
+ return normalizeOutputPath(decodeURIComponent(resolved));
47
+ } catch {
48
+ return resolved;
49
+ }
50
+ }
51
+
52
+ function getDirectoryIndexPath(pathname: string): string {
53
+ return normalizeOutputPath(path.posix.join(pathname, 'index.html'));
54
+ }
55
+
56
+ function hasMappedCodeExtension(
57
+ pathname: string,
58
+ codeExtensions: string[],
59
+ ): boolean {
60
+ const lower = pathname.toLowerCase();
61
+ return codeExtensions.some(ext => lower.endsWith(`.${ext.toLowerCase()}`));
62
+ }
63
+
64
+ function rewriteAbsoluteHrefWithBasePath(
65
+ href: string,
66
+ applyBasePath: (subPath: string) => string,
67
+ ): string {
68
+ const { pathname, suffix } = splitHref(href);
69
+ if (!pathname.startsWith('/') || pathname.startsWith('//')) {
70
+ return href;
71
+ }
72
+ return `${applyBasePath(pathname)}${suffix}`;
73
+ }
74
+
75
+ function rewriteAbsoluteSrcWithBasePath(
76
+ src: string,
77
+ applyBasePath: (subPath: string) => string,
78
+ ): string {
79
+ if (!src.startsWith('/') || src.startsWith('//')) {
80
+ return src;
81
+ }
82
+ return applyBasePath(src);
83
+ }
84
+
85
+ function resolveAnchorTarget({
86
+ href,
87
+ sourceUrlPath,
88
+ validInternalTargets,
89
+ codeExtensions,
90
+ literateJavaOutputPaths,
91
+ skipCodeLinkRewrite,
92
+ }: {
93
+ href: string;
94
+ sourceUrlPath: string;
95
+ validInternalTargets: Set<string>;
96
+ codeExtensions: string[];
97
+ literateJavaOutputPaths?: Set<string>;
98
+ skipCodeLinkRewrite: boolean;
99
+ }): { finalHref: string; resolvedTarget: string | null } {
100
+ if (!isInternalLink(href)) {
101
+ return { finalHref: href, resolvedTarget: null };
102
+ }
103
+
104
+ const { pathname, suffix } = splitHref(href);
105
+ const resolvedPath = resolvePathname(sourceUrlPath, pathname);
106
+ if (!resolvedPath) {
107
+ return { finalHref: href, resolvedTarget: null };
108
+ }
109
+
110
+ let finalPathname = pathname;
111
+ let resolvedTarget = resolvedPath;
112
+ const mappedCodePath =
113
+ !skipCodeLinkRewrite && hasMappedCodeExtension(pathname, codeExtensions);
114
+
115
+ if (mappedCodePath) {
116
+ const htmlTarget = `${resolvedPath}.html`;
117
+ if (literateJavaOutputPaths?.has(resolvedPath)) {
118
+ resolvedTarget = resolvedPath;
119
+ } else if (validInternalTargets.has(htmlTarget)) {
120
+ finalPathname = `${pathname}.html`;
121
+ resolvedTarget = htmlTarget;
122
+ } else if (validInternalTargets.has(resolvedPath)) {
123
+ resolvedTarget = resolvedPath;
124
+ } else {
125
+ resolvedTarget = htmlTarget;
126
+ }
127
+ }
128
+
129
+ return { finalHref: `${finalPathname}${suffix}`, resolvedTarget };
130
+ }
131
+
132
+ export function finalizeHtmlPage({
133
+ filePath,
134
+ html,
135
+ siteVariables,
136
+ sourceUrlPath,
137
+ validInternalTargets,
138
+ literateJavaOutputPaths,
139
+ dependencyCollector,
140
+ }: FinalizeHtmlPageOptions): FinalizedHtmlPage {
141
+ const dom = new JSDOM(html);
142
+ const document = dom.window.document;
143
+ const applyBasePath = createApplyBasePath(siteVariables);
144
+ const codeExtensions = Object.keys(
145
+ getExtensionToShikiLanguage(siteVariables),
146
+ );
147
+ const analysis: HtmlOutputAnalysis = { outgoingTargets: new Set() };
148
+
149
+ for (const element of document.querySelectorAll('[href]')) {
150
+ const href = element.getAttribute('href');
151
+ if (!href) {
152
+ continue;
153
+ }
154
+
155
+ const isAnchor = element.tagName === 'A';
156
+ const isContentAnchor = isAnchor && element.closest('main.body') !== null;
157
+
158
+ if (isContentAnchor) {
159
+ const { finalHref, resolvedTarget } = resolveAnchorTarget({
160
+ href,
161
+ sourceUrlPath,
162
+ validInternalTargets,
163
+ codeExtensions,
164
+ literateJavaOutputPaths,
165
+ skipCodeLinkRewrite: element.hasAttribute('download'),
166
+ });
167
+ const rewrittenHref = finalHref.startsWith('/')
168
+ ? rewriteAbsoluteHrefWithBasePath(finalHref, applyBasePath)
169
+ : finalHref;
170
+
171
+ if (rewrittenHref !== href) {
172
+ element.setAttribute('href', rewrittenHref);
173
+ }
174
+
175
+ if (resolvedTarget) {
176
+ if (isAnchor) {
177
+ analysis.outgoingTargets.add(resolvedTarget);
178
+ }
179
+
180
+ const directoryIndexPath = getDirectoryIndexPath(resolvedTarget);
181
+ if (
182
+ directoryIndexPath !== resolvedTarget &&
183
+ validInternalTargets.has(directoryIndexPath)
184
+ ) {
185
+ throw new Error(
186
+ `${filePath}: directory link must reference index.html explicitly: "${href}" (resolved to "${resolvedTarget}", expected "${directoryIndexPath}")`,
187
+ );
188
+ }
189
+
190
+ if (!validInternalTargets.has(resolvedTarget)) {
191
+ throw new Error(
192
+ `${filePath}: broken internal link: "${href}" (resolved to "${resolvedTarget}")`,
193
+ );
194
+ }
195
+
196
+ dependencyCollector?.internalTargets?.add(resolvedTarget);
197
+ }
198
+
199
+ continue;
200
+ }
201
+
202
+ if (isAnchor && isInternalLink(href)) {
203
+ const { pathname } = splitHref(href);
204
+ const resolvedTarget = resolvePathname(sourceUrlPath, pathname);
205
+ if (resolvedTarget) {
206
+ analysis.outgoingTargets.add(resolvedTarget);
207
+ }
208
+ }
209
+
210
+ const rewrittenHref = rewriteAbsoluteHrefWithBasePath(href, applyBasePath);
211
+ if (rewrittenHref !== href) {
212
+ element.setAttribute('href', rewrittenHref);
213
+ }
214
+ }
215
+
216
+ for (const element of document.querySelectorAll('[src]')) {
217
+ const src = element.getAttribute('src');
218
+ if (!src) {
219
+ continue;
220
+ }
221
+
222
+ const rewrittenSrc = rewriteAbsoluteSrcWithBasePath(src, applyBasePath);
223
+ if (rewrittenSrc !== src) {
224
+ element.setAttribute('src', rewrittenSrc);
225
+ }
226
+ }
227
+
228
+ const doctype = document.doctype;
229
+ const doctypePrefix = doctype
230
+ ? `<!DOCTYPE ${doctype.name}${
231
+ doctype.publicId ? ` PUBLIC "${doctype.publicId}"` : ''
232
+ }${doctype.systemId ? ` "${doctype.systemId}"` : ''}>`
233
+ : '';
234
+
235
+ return {
236
+ html: `${doctypePrefix}${document.documentElement.outerHTML}`,
237
+ analysis,
238
+ };
239
+ }
@@ -1,6 +1,6 @@
1
1
  import MarkdownIt from 'markdown-it';
2
2
  import type { Options } from 'markdown-it/lib/index.mjs';
3
- import type Token from 'markdown-it/lib/token.mjs';
3
+ import Token from 'markdown-it/lib/token.mjs';
4
4
  import type Renderer from 'markdown-it/lib/renderer.mjs';
5
5
  import { convertMarkdown as curlyQuote } from 'quote-quote';
6
6
  import markdownItAnchor from 'markdown-it-anchor';
@@ -13,8 +13,6 @@ import { isBundledLanguage, isPlainTextLanguage } from '../site-variables';
13
13
  import headingSubtitlePlugin from '../heading-subtitle-plugin';
14
14
  import deflistIdPlugin from '../deflist-id-plugin';
15
15
  import externalLinksPlugin from '../external-links-plugin';
16
- import validateInternalLinksPlugin from '../validate-internal-links-plugin';
17
- import applyBasePathPlugin from '../apply-base-path-plugin';
18
16
  import { tocPlugin } from '../toc-plugin';
19
17
  import columnsPlugin from '../columns-plugin';
20
18
  import markdownItKatex from '@vscode/markdown-it-katex';
@@ -27,8 +25,9 @@ const ALERT_PATTERN = /^(note|warning)(?:\s+"(.+)"|\s+(.+))?$/;
27
25
  const QUESTION_PATTERN = /^question\s+(.+)$/;
28
26
 
29
27
  interface CreateMarkdownOptions {
30
- validatorOptions?: Record<string, unknown>;
31
28
  filePath?: string;
29
+ slides?: boolean;
30
+ validatorOptions?: Record<string, unknown>;
32
31
  literateJavaOutputPaths?: Set<string>;
33
32
  sourceUrlPath?: string;
34
33
  validTargets?: Set<string>;
@@ -58,13 +57,7 @@ export function createMarkdown(
58
57
  siteVariables: SiteVariables,
59
58
  options: CreateMarkdownOptions = {},
60
59
  ): MarkdownIt {
61
- const {
62
- validatorOptions = {},
63
- filePath,
64
- literateJavaOutputPaths,
65
- sourceUrlPath,
66
- validTargets,
67
- } = options;
60
+ const { filePath, slides = false } = options;
68
61
  const markdown = new MarkdownIt({ html: true, typographer: true })
69
62
  .use(headingSubtitlePlugin)
70
63
  .use(markdownItAnchor, { tabIndex: false })
@@ -72,12 +65,6 @@ export function createMarkdown(
72
65
  .use(markdownItDeflist)
73
66
  .use(deflistIdPlugin)
74
67
  .use(externalLinksPlugin, siteVariables)
75
- .use(validateInternalLinksPlugin, validatorOptions)
76
- .use(applyBasePathPlugin, siteVariables, {
77
- literateJavaOutputPaths,
78
- sourceUrlPath,
79
- validTargets,
80
- })
81
68
  .use(tocPlugin)
82
69
  .use(columnsPlugin)
83
70
  .use(markdownItKatex)
@@ -115,6 +102,232 @@ export function createMarkdown(
115
102
  },
116
103
  });
117
104
 
105
+ function createWrapperToken(
106
+ type: string,
107
+ nesting: -1 | 0 | 1,
108
+ attributes: [string, string][],
109
+ ): Token {
110
+ const token = new Token(type, 'div', nesting);
111
+ token.attrs = attributes;
112
+ return token;
113
+ }
114
+
115
+ function isInlineOnlyTokenStream(tokens: Token[]): boolean {
116
+ return (
117
+ tokens.length === 1 &&
118
+ tokens[0]?.type === 'inline' &&
119
+ tokens[0].level === 0
120
+ );
121
+ }
122
+
123
+ function findTagEnd(html: string, start: number): number {
124
+ let quote: '"' | "'" | null = null;
125
+
126
+ for (let i = start + 1; i < html.length; i++) {
127
+ const char = html[i];
128
+ if (quote) {
129
+ if (char === quote) {
130
+ quote = null;
131
+ }
132
+ continue;
133
+ }
134
+
135
+ if (char === '"' || char === "'") {
136
+ quote = char;
137
+ continue;
138
+ }
139
+
140
+ if (char === '>') {
141
+ return i;
142
+ }
143
+ }
144
+
145
+ return -1;
146
+ }
147
+
148
+ function stripActualHrTags(html: string): string {
149
+ const rawTextTags = new Set(['script', 'style', 'title', 'textarea']);
150
+ const lowerHtml = html.toLowerCase();
151
+ let result = '';
152
+ let index = 0;
153
+ let rawTextTag: string | null = null;
154
+
155
+ while (index < html.length) {
156
+ if (rawTextTag) {
157
+ const closeStart = lowerHtml.indexOf(`</${rawTextTag}`, index);
158
+ if (closeStart === -1) {
159
+ result += html.slice(index);
160
+ break;
161
+ }
162
+
163
+ result += html.slice(index, closeStart);
164
+ const closeEnd = findTagEnd(html, closeStart);
165
+ if (closeEnd === -1) {
166
+ result += html.slice(closeStart);
167
+ break;
168
+ }
169
+
170
+ result += html.slice(closeStart, closeEnd + 1);
171
+ index = closeEnd + 1;
172
+ rawTextTag = null;
173
+ continue;
174
+ }
175
+
176
+ const openStart = html.indexOf('<', index);
177
+ if (openStart === -1) {
178
+ result += html.slice(index);
179
+ break;
180
+ }
181
+
182
+ result += html.slice(index, openStart);
183
+
184
+ if (html.startsWith('<!--', openStart)) {
185
+ const commentEnd = html.indexOf('-->', openStart + 4);
186
+ if (commentEnd === -1) {
187
+ result += html.slice(openStart);
188
+ break;
189
+ }
190
+
191
+ result += html.slice(openStart, commentEnd + 3);
192
+ index = commentEnd + 3;
193
+ continue;
194
+ }
195
+
196
+ let tagStart = openStart + 1;
197
+ let isClosingTag = false;
198
+ if (html[tagStart] === '/') {
199
+ isClosingTag = true;
200
+ tagStart++;
201
+ }
202
+
203
+ if (!/[A-Za-z]/.test(html[tagStart] ?? '')) {
204
+ result += '<';
205
+ index = openStart + 1;
206
+ continue;
207
+ }
208
+
209
+ let nameEnd = tagStart;
210
+ while (/[A-Za-z0-9:-]/.test(html[nameEnd] ?? '')) {
211
+ nameEnd++;
212
+ }
213
+
214
+ const tagName = html.slice(tagStart, nameEnd).toLowerCase();
215
+ const tagEnd = findTagEnd(html, openStart);
216
+ if (tagEnd === -1) {
217
+ result += html.slice(openStart);
218
+ break;
219
+ }
220
+
221
+ const tagText = html.slice(openStart, tagEnd + 1);
222
+ const tagBody = html.slice(openStart + 1, tagEnd);
223
+ const isSelfClosing = /\/\s*$/.test(tagBody);
224
+
225
+ if (tagName === 'hr' && !isClosingTag) {
226
+ index = tagEnd + 1;
227
+ continue;
228
+ }
229
+
230
+ result += tagText;
231
+ index = tagEnd + 1;
232
+
233
+ if (!isClosingTag && !isSelfClosing && rawTextTags.has(tagName)) {
234
+ rawTextTag = tagName;
235
+ }
236
+ }
237
+
238
+ return result;
239
+ }
240
+
241
+ function sanitizeTokenForSlides(token: Token): Token | null {
242
+ if (token.type === 'inline') {
243
+ token.content = stripActualHrTags(token.content);
244
+ if (token.children) {
245
+ const sanitizedChildren: Token[] = [];
246
+ for (const child of token.children) {
247
+ const sanitizedChild = sanitizeTokenForSlides(child);
248
+ if (sanitizedChild) {
249
+ sanitizedChildren.push(sanitizedChild);
250
+ }
251
+ }
252
+ token.children = sanitizedChildren;
253
+ }
254
+ return token;
255
+ }
256
+
257
+ if (token.type !== 'html_block' && token.type !== 'html_inline') {
258
+ return token;
259
+ }
260
+
261
+ const strippedContent = stripActualHrTags(token.content);
262
+ if (strippedContent.trim() === '') {
263
+ return null;
264
+ }
265
+
266
+ token.content = strippedContent;
267
+ return token;
268
+ }
269
+
270
+ markdown.core.ruler.before('toc_collector', 'slides_transform', state => {
271
+ if (!slides) {
272
+ return;
273
+ }
274
+
275
+ if (isInlineOnlyTokenStream(state.tokens)) {
276
+ return;
277
+ }
278
+
279
+ const env = (state.env ??= {}) as Record<string, unknown>;
280
+ const slideGroups: Token[][] = [];
281
+ let currentSlide: Token[] = [];
282
+
283
+ for (const token of state.tokens) {
284
+ const sanitizedToken = sanitizeTokenForSlides(token);
285
+ if (!sanitizedToken) {
286
+ continue;
287
+ }
288
+
289
+ if (sanitizedToken.type === 'hr') {
290
+ if (sanitizedToken.level === 0) {
291
+ if (currentSlide.length > 0) {
292
+ slideGroups.push(currentSlide);
293
+ currentSlide = [];
294
+ }
295
+ }
296
+ continue;
297
+ }
298
+
299
+ currentSlide.push(sanitizedToken);
300
+ }
301
+
302
+ if (currentSlide.length > 0) {
303
+ slideGroups.push(currentSlide);
304
+ }
305
+
306
+ const transformedTokens = [
307
+ createWrapperToken('slide_deck_open', 1, [
308
+ ['class', 'slide-deck'],
309
+ ['data-slides-root', ''],
310
+ ]),
311
+ ];
312
+
313
+ for (const [slideIndex, slideTokens] of slideGroups.entries()) {
314
+ transformedTokens.push(
315
+ createWrapperToken('slide_open', 1, [
316
+ ['class', 'slide'],
317
+ ['data-slide-index', String(slideIndex)],
318
+ ]),
319
+ );
320
+ transformedTokens.push(...slideTokens);
321
+ transformedTokens.push(createWrapperToken('slide_close', -1, []));
322
+ }
323
+
324
+ transformedTokens.push(createWrapperToken('slide_deck_close', -1, []));
325
+
326
+ env.slides = true;
327
+ env.slideCount = slideGroups.length;
328
+ state.tokens = transformedTokens;
329
+ });
330
+
118
331
  // The @vscode/markdown-it-katex plugin's renderers swallow errors thrown
119
332
  // by katex.renderToString() and return error HTML instead of propagating
120
333
  // the exception. Override the renderers so parse errors fail the build.
@@ -351,6 +564,13 @@ export function createMarkdown(
351
564
 
352
565
  markdown.renderer.rules.hidden_fence = () => '';
353
566
 
567
+ markdown.renderer.rules.slide_deck_open = () =>
568
+ '<div class="slide-deck" data-slides-root>';
569
+ markdown.renderer.rules.slide_deck_close = () => '</div>';
570
+ markdown.renderer.rules.slide_open = (tokens, idx) =>
571
+ `<div class="slide" data-slide-index="${tokens[idx].attrGet('data-slide-index') ?? ''}">`;
572
+ markdown.renderer.rules.slide_close = () => '</div>';
573
+
354
574
  markdown.renderer.rules.fence = (tokens, idx) => {
355
575
  const token = tokens[idx];
356
576
  const lang = token.info.trim().split(/\s+/)[0] || 'text';
@@ -1,4 +1,5 @@
1
1
  import path from 'path';
2
+ import { globals } from '../globals';
2
3
  import type { SiteVariables } from '../types';
3
4
 
4
5
  export function getPackageDir(): string {
@@ -6,7 +7,7 @@ export function getPackageDir(): string {
6
7
  }
7
8
 
8
9
  export function getProjectDir(): string {
9
- return process.cwd();
10
+ return globals.cwd();
10
11
  }
11
12
 
12
13
  export function getContentDir(): string {