@tutorialkit-rb/astro 1.5.2-rb.0.1.0
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.
- package/README.md +14 -0
- package/dist/default/components/DownloadButton.tsx +44 -0
- package/dist/default/components/HeadTags.astro +3 -0
- package/dist/default/components/LoginButton.tsx +55 -0
- package/dist/default/components/Logo.astro +30 -0
- package/dist/default/components/MainContainer.astro +86 -0
- package/dist/default/components/MetaTags.astro +44 -0
- package/dist/default/components/MobileContentToggle.astro +44 -0
- package/dist/default/components/NavCard.astro +23 -0
- package/dist/default/components/NavWrapper.tsx +11 -0
- package/dist/default/components/OpenInStackblitzLink.tsx +37 -0
- package/dist/default/components/PageLoadingIndicator.astro +66 -0
- package/dist/default/components/ResizablePanel.astro +247 -0
- package/dist/default/components/ThemeSwitch.tsx +24 -0
- package/dist/default/components/TopBar.astro +20 -0
- package/dist/default/components/TopBarWrapper.astro +30 -0
- package/dist/default/components/TutorialContent.astro +48 -0
- package/dist/default/components/WorkspacePanelWrapper.tsx +25 -0
- package/dist/default/components/setup.ts +20 -0
- package/dist/default/components/webcontainer.ts +46 -0
- package/dist/default/env-default.d.ts +19 -0
- package/dist/default/layouts/Layout.astro +98 -0
- package/dist/default/pages/[...slug].astro +39 -0
- package/dist/default/pages/index.astro +25 -0
- package/dist/default/stores/auth-store.ts +6 -0
- package/dist/default/stores/theme-store.ts +32 -0
- package/dist/default/stores/view-store.ts +5 -0
- package/dist/default/styles/base.css +11 -0
- package/dist/default/styles/markdown.css +400 -0
- package/dist/default/styles/panel.css +7 -0
- package/dist/default/styles/variables.css +396 -0
- package/dist/default/utils/constants.ts +6 -0
- package/dist/default/utils/content/files-ref.ts +25 -0
- package/dist/default/utils/content/squash.ts +37 -0
- package/dist/default/utils/content.ts +446 -0
- package/dist/default/utils/logger.ts +56 -0
- package/dist/default/utils/logo.ts +17 -0
- package/dist/default/utils/nav.ts +65 -0
- package/dist/default/utils/publicAsset.ts +27 -0
- package/dist/default/utils/routes.ts +34 -0
- package/dist/default/utils/url.ts +22 -0
- package/dist/default/utils/workspace.ts +31 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +972 -0
- package/dist/integrations.d.ts +10 -0
- package/dist/remark/callouts.d.ts +3 -0
- package/dist/remark/import-file.d.ts +7 -0
- package/dist/remark/index.d.ts +2 -0
- package/dist/types.d.ts +9 -0
- package/dist/utils.d.ts +2 -0
- package/dist/vite-plugins/core.d.ts +2 -0
- package/dist/vite-plugins/css.d.ts +4 -0
- package/dist/vite-plugins/override-components.d.ts +78 -0
- package/dist/vite-plugins/store.d.ts +2 -0
- package/dist/webcontainer-files/cache.d.ts +21 -0
- package/dist/webcontainer-files/cache.spec.d.ts +1 -0
- package/dist/webcontainer-files/constants.d.ts +4 -0
- package/dist/webcontainer-files/filesmap.d.ts +38 -0
- package/dist/webcontainer-files/filesmap.spec.d.ts +1 -0
- package/dist/webcontainer-files/index.d.ts +8 -0
- package/dist/webcontainer-files/utils.d.ts +6 -0
- package/package.json +85 -0
- package/types.d.ts +12 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3
|
+
|
|
4
|
+
// src/integrations.ts
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import mdx from "@astrojs/mdx";
|
|
7
|
+
import react from "@astrojs/react";
|
|
8
|
+
import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
|
|
9
|
+
import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
|
|
10
|
+
import { getInlineContentForPackage } from "@tutorialkit-rb/theme";
|
|
11
|
+
import expressiveCode from "astro-expressive-code";
|
|
12
|
+
import UnoCSS from "unocss/astro";
|
|
13
|
+
function extraIntegrations({
|
|
14
|
+
root,
|
|
15
|
+
expressiveCodePlugins = []
|
|
16
|
+
}) {
|
|
17
|
+
return [
|
|
18
|
+
react(),
|
|
19
|
+
expressiveCode({
|
|
20
|
+
plugins: [pluginCollapsibleSections(), pluginLineNumbers(), ...expressiveCodePlugins],
|
|
21
|
+
themes: ["dark-plus", "light-plus"],
|
|
22
|
+
customizeTheme: (theme) => {
|
|
23
|
+
const isDark = theme.type === "dark";
|
|
24
|
+
theme.styleOverrides = {
|
|
25
|
+
borderColor: "var(--tk-border-secondary)",
|
|
26
|
+
borderWidth: "1px",
|
|
27
|
+
borderRadius: "var(--code-border-radius, 0px)",
|
|
28
|
+
frames: {
|
|
29
|
+
terminalTitlebarBackground: `var(--tk-background-${isDark ? "primary" : "secondary"})`,
|
|
30
|
+
terminalTitlebarBorderBottomColor: `var(--tk-background-${isDark ? "primary" : "secondary"})`,
|
|
31
|
+
editorTabBorderRadius: "var(--code-border-radius, 0px)",
|
|
32
|
+
editorTabBarBackground: `var(--tk-background-${isDark ? "primary" : "secondary"})`
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
themeCssSelector: (theme) => {
|
|
37
|
+
let customThemeName = "light";
|
|
38
|
+
if (theme.name === "dark-plus") {
|
|
39
|
+
customThemeName = "dark";
|
|
40
|
+
}
|
|
41
|
+
return `[data-theme='${customThemeName}']`;
|
|
42
|
+
},
|
|
43
|
+
defaultProps: {
|
|
44
|
+
showLineNumbers: false
|
|
45
|
+
},
|
|
46
|
+
styleOverrides: {
|
|
47
|
+
frames: {
|
|
48
|
+
shadowColor: "none"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
mdx(),
|
|
53
|
+
UnoCSS({
|
|
54
|
+
configDeps: ["./theme.ts"],
|
|
55
|
+
injectReset: createRequire(root).resolve("@unocss/reset/tailwind.css"),
|
|
56
|
+
content: {
|
|
57
|
+
inline: getInlineContentForPackage({
|
|
58
|
+
name: "@tutorialkit-rb/astro",
|
|
59
|
+
pattern: "/dist/default/**/*.astro",
|
|
60
|
+
root
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/remark/index.ts
|
|
68
|
+
import path2 from "node:path";
|
|
69
|
+
import remarkDirective from "remark-directive";
|
|
70
|
+
|
|
71
|
+
// src/remark/callouts.ts
|
|
72
|
+
import { h } from "hastscript";
|
|
73
|
+
import {
|
|
74
|
+
directiveToMarkdown
|
|
75
|
+
} from "mdast-util-directive";
|
|
76
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
77
|
+
import { visit } from "unist-util-visit";
|
|
78
|
+
var callouts = {
|
|
79
|
+
tip: {
|
|
80
|
+
title: "Tip",
|
|
81
|
+
icon: "i-ph-rocket-launch"
|
|
82
|
+
},
|
|
83
|
+
info: {
|
|
84
|
+
title: "Info",
|
|
85
|
+
icon: "i-ph-info"
|
|
86
|
+
},
|
|
87
|
+
warn: {
|
|
88
|
+
title: "Warning",
|
|
89
|
+
icon: "i-ph-warning-circle"
|
|
90
|
+
},
|
|
91
|
+
success: {
|
|
92
|
+
title: "Success",
|
|
93
|
+
icon: "i-ph-check-circle"
|
|
94
|
+
},
|
|
95
|
+
danger: {
|
|
96
|
+
title: "Danger",
|
|
97
|
+
icon: "i-ph-x-circle"
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var variants = /* @__PURE__ */ new Set(["tip", "info", "warn", "danger", "success"]);
|
|
101
|
+
function isNodeDirective(node) {
|
|
102
|
+
return node.type === "textDirective" || node.type === "leafDirective" || node.type === "containerDirective";
|
|
103
|
+
}
|
|
104
|
+
function isContainerDirective(node) {
|
|
105
|
+
return node.type === "containerDirective";
|
|
106
|
+
}
|
|
107
|
+
function transformUnhandledDirective(node, index, parent) {
|
|
108
|
+
const textNode = {
|
|
109
|
+
type: "text",
|
|
110
|
+
value: toMarkdown(node, { extensions: [directiveToMarkdown()] })
|
|
111
|
+
};
|
|
112
|
+
if (node.type === "textDirective") {
|
|
113
|
+
parent.children[index] = textNode;
|
|
114
|
+
} else {
|
|
115
|
+
parent.children[index] = {
|
|
116
|
+
type: "paragraph",
|
|
117
|
+
children: [textNode]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function remarkCalloutsPlugin() {
|
|
122
|
+
return () => {
|
|
123
|
+
return (tree) => {
|
|
124
|
+
visit(tree, (node, index, parent) => {
|
|
125
|
+
if (!parent || index === void 0 || !isNodeDirective(node)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (node.type === "textDirective" || node.type === "leafDirective") {
|
|
129
|
+
transformUnhandledDirective(node, index, parent);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (isContainerDirective(node)) {
|
|
133
|
+
if (!variants.has(node.name)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const variant = node.name;
|
|
137
|
+
const callout = callouts[variant];
|
|
138
|
+
const data = node.data || (node.data = {});
|
|
139
|
+
const attributes = node.attributes ?? {};
|
|
140
|
+
const title = attributes.title ?? callout.title;
|
|
141
|
+
const noBorder = attributes.noBorder === "true";
|
|
142
|
+
const hideTitle = attributes.hideTitle === "true";
|
|
143
|
+
const hideIcon = attributes.hideIcon === "true";
|
|
144
|
+
const classes = [
|
|
145
|
+
`callout callout-${variant} my-4 flex flex-col p-3 bg-tk-elements-markdown-callouts-backgroundColor text-tk-elements-markdown-callouts-textColor`,
|
|
146
|
+
attributes.class ?? "",
|
|
147
|
+
...noBorder ? [] : ["border-l-3", "border-tk-elements-markdown-callouts-borderColor"]
|
|
148
|
+
];
|
|
149
|
+
node.attributes = {
|
|
150
|
+
...attributes,
|
|
151
|
+
class: classes.filter((calloutClass) => !!calloutClass).join(" "),
|
|
152
|
+
"aria-label": title
|
|
153
|
+
};
|
|
154
|
+
node.children = generate(title, node.children, callout, hideIcon, hideTitle);
|
|
155
|
+
const tagName = "aside";
|
|
156
|
+
const hast = h(tagName, node.attributes);
|
|
157
|
+
data.hName = hast.tagName;
|
|
158
|
+
data.hProperties = hast.properties;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function generate(title, children, callout, hideIcon, hideTitle) {
|
|
165
|
+
return [
|
|
166
|
+
...hideTitle ? [] : [
|
|
167
|
+
{
|
|
168
|
+
type: "paragraph",
|
|
169
|
+
data: {
|
|
170
|
+
hName: "div",
|
|
171
|
+
hProperties: {
|
|
172
|
+
className: "w-full flex gap-2 items-center text-tk-elements-markdown-callouts-titleTextColor",
|
|
173
|
+
ariaHidden: true
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
children: [
|
|
177
|
+
...hideIcon ? [] : [
|
|
178
|
+
{
|
|
179
|
+
type: "html",
|
|
180
|
+
value: `<span class="text-6 inline-block text-tk-elements-markdown-callouts-iconColor ${callout.icon}"></span>`
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
{
|
|
184
|
+
type: "html",
|
|
185
|
+
value: `<span class="text-4 font-semibold inline-block"> ${title}</span>`
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
{
|
|
191
|
+
type: "paragraph",
|
|
192
|
+
data: {
|
|
193
|
+
hName: "section",
|
|
194
|
+
hProperties: { className: ["callout-content mt-1"] }
|
|
195
|
+
},
|
|
196
|
+
children
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/remark/import-file.ts
|
|
202
|
+
import fs from "node:fs";
|
|
203
|
+
import path from "node:path";
|
|
204
|
+
import frontMatter from "front-matter";
|
|
205
|
+
import * as kleur from "kleur/colors";
|
|
206
|
+
import { visit as visit2 } from "unist-util-visit";
|
|
207
|
+
function remarkImportFilePlugin(options) {
|
|
208
|
+
return () => {
|
|
209
|
+
return (tree, file) => {
|
|
210
|
+
if (!file.basename || !/^content\.mdx?$/.test(file.basename)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const cwd = path.dirname(file.path);
|
|
214
|
+
const templateName = getTemplateName(file.path);
|
|
215
|
+
const templatesPath = path.join(options.templatesPath, templateName);
|
|
216
|
+
visit2(tree, (node) => {
|
|
217
|
+
if (node.type !== "code") {
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
if (node.lang && /^(files?|solution):/.test(node.lang)) {
|
|
221
|
+
const relativeFilePath = node.lang.replace(/^(files?|solution):\//, "");
|
|
222
|
+
let content;
|
|
223
|
+
if (/^files?\:/.test(node.lang)) {
|
|
224
|
+
content = tryRead(path.join(cwd, "_files", relativeFilePath), false);
|
|
225
|
+
if (!content) {
|
|
226
|
+
content = tryRead(path.join(templatesPath, relativeFilePath));
|
|
227
|
+
}
|
|
228
|
+
} else if (node.lang.startsWith("solution:")) {
|
|
229
|
+
content = tryRead(path.join(cwd, "_solution", relativeFilePath));
|
|
230
|
+
}
|
|
231
|
+
if (content) {
|
|
232
|
+
node.value = content;
|
|
233
|
+
node.meta ||= "";
|
|
234
|
+
node.meta += " path=" + node.lang;
|
|
235
|
+
node.lang = path.extname(relativeFilePath).slice(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return void 0;
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function tryRead(filePath, warn = true) {
|
|
244
|
+
try {
|
|
245
|
+
return fs.readFileSync(filePath, "utf8");
|
|
246
|
+
} catch {
|
|
247
|
+
if (warn) {
|
|
248
|
+
printWarning(`Could not read '${filePath}'. Are you sure this file exists?`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
function getTemplateName(file) {
|
|
254
|
+
const content = fs.readFileSync(file, "utf8");
|
|
255
|
+
const meta = frontMatter(
|
|
256
|
+
content
|
|
257
|
+
);
|
|
258
|
+
if (meta.attributes.template) {
|
|
259
|
+
return meta.attributes.template;
|
|
260
|
+
}
|
|
261
|
+
if (meta.attributes.type === "tutorial") {
|
|
262
|
+
return "default";
|
|
263
|
+
}
|
|
264
|
+
return getTemplateName(path.join(path.dirname(path.dirname(file)), "meta.md"));
|
|
265
|
+
}
|
|
266
|
+
function printWarning(message) {
|
|
267
|
+
const time = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
|
|
268
|
+
console.warn(kleur.yellow(`${kleur.bold(time)} [WARN] [remarkImportFilePlugin] ${message}`));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/remark/index.ts
|
|
272
|
+
function updateMarkdownConfig({ updateConfig }) {
|
|
273
|
+
updateConfig({
|
|
274
|
+
markdown: {
|
|
275
|
+
remarkPlugins: [
|
|
276
|
+
remarkDirective,
|
|
277
|
+
remarkCalloutsPlugin(),
|
|
278
|
+
remarkImportFilePlugin({ templatesPath: path2.join(process.cwd(), "src/templates") })
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/vite-plugins/core.ts
|
|
285
|
+
import path3 from "node:path";
|
|
286
|
+
import { fileURLToPath } from "node:url";
|
|
287
|
+
|
|
288
|
+
// src/utils.ts
|
|
289
|
+
function withResolvers() {
|
|
290
|
+
let resolve;
|
|
291
|
+
let reject;
|
|
292
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
293
|
+
resolve = _resolve;
|
|
294
|
+
reject = _reject;
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
resolve,
|
|
298
|
+
reject,
|
|
299
|
+
promise
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function normalizeImportPath(importPath) {
|
|
303
|
+
return importPath.replaceAll("\\", "/");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/vite-plugins/core.ts
|
|
307
|
+
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
308
|
+
var virtualModuleId = "tutorialkit:core";
|
|
309
|
+
var resolvedVirtualModuleId = `${virtualModuleId}`;
|
|
310
|
+
var tutorialkitCore = {
|
|
311
|
+
name: "tutorialkit-core-virtual-mod-plugin",
|
|
312
|
+
resolveId(id) {
|
|
313
|
+
if (id === virtualModuleId) {
|
|
314
|
+
return resolvedVirtualModuleId;
|
|
315
|
+
}
|
|
316
|
+
return void 0;
|
|
317
|
+
},
|
|
318
|
+
async load(id) {
|
|
319
|
+
if (id === resolvedVirtualModuleId) {
|
|
320
|
+
const pathToInit = normalizeImportPath(path3.join(__dirname, "default/components/webcontainer.ts"));
|
|
321
|
+
return `
|
|
322
|
+
export { webcontainer } from '${pathToInit}';
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// src/vite-plugins/css.ts
|
|
330
|
+
import fs2 from "node:fs/promises";
|
|
331
|
+
import path4 from "node:path";
|
|
332
|
+
import { watch } from "chokidar";
|
|
333
|
+
var CUSTOM_PATHS = ["theme.css", "theme/index.css"];
|
|
334
|
+
var virtualModuleId2 = "@tutorialkit/custom.css";
|
|
335
|
+
var resolvedVirtualModuleId2 = `/${virtualModuleId2}`;
|
|
336
|
+
var projectRoot = process.cwd();
|
|
337
|
+
var userlandCSS = {
|
|
338
|
+
name: "@tutorialkit/custom.css",
|
|
339
|
+
resolveId(id) {
|
|
340
|
+
if (id === virtualModuleId2) {
|
|
341
|
+
return resolvedVirtualModuleId2;
|
|
342
|
+
}
|
|
343
|
+
return void 0;
|
|
344
|
+
},
|
|
345
|
+
configResolved({ root }) {
|
|
346
|
+
projectRoot = root;
|
|
347
|
+
},
|
|
348
|
+
async load(id) {
|
|
349
|
+
if (id === resolvedVirtualModuleId2) {
|
|
350
|
+
const path9 = await findCustomCSSFilePath(projectRoot);
|
|
351
|
+
if (path9) {
|
|
352
|
+
return `@import '${path9}';`;
|
|
353
|
+
} else {
|
|
354
|
+
return "";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
function watchUserlandCSS(server, logger) {
|
|
361
|
+
const projectRoot2 = server.config.root;
|
|
362
|
+
const watchedNames = new Map(CUSTOM_PATHS.map((path9) => [path9, false]));
|
|
363
|
+
const watcher = watch(projectRoot2, {
|
|
364
|
+
ignoreInitial: true,
|
|
365
|
+
cwd: projectRoot2,
|
|
366
|
+
depth: 2,
|
|
367
|
+
ignored: ["dist", "src", "node_modules"]
|
|
368
|
+
});
|
|
369
|
+
watcher.on("all", (eventName, filePath) => {
|
|
370
|
+
if (eventName === "addDir" || eventName === "unlinkDir" || !watchedNames.has(filePath)) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
watchedNames.set(filePath, eventName !== "unlink");
|
|
374
|
+
checkConflicts(watchedNames, logger);
|
|
375
|
+
const module = server.moduleGraph.getModuleById(resolvedVirtualModuleId2);
|
|
376
|
+
if (module) {
|
|
377
|
+
server.reloadModule(module);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
async function findCustomCSSFilePath(projectRoot2) {
|
|
382
|
+
for (const cssFilePath of CUSTOM_PATHS) {
|
|
383
|
+
try {
|
|
384
|
+
const stats = await fs2.stat(path4.join(projectRoot2, cssFilePath));
|
|
385
|
+
if (stats.isFile()) {
|
|
386
|
+
return path4.resolve(cssFilePath);
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
function checkConflicts(watchedNames, logger) {
|
|
394
|
+
const conflictCount = [...watchedNames.values()].reduce((count, value) => value ? count + 1 : count, 0);
|
|
395
|
+
if (conflictCount > 1) {
|
|
396
|
+
let errorMessage = "";
|
|
397
|
+
let index = 0;
|
|
398
|
+
const lastIndex = conflictCount - 1;
|
|
399
|
+
for (const [configName, isPresent] of watchedNames.entries()) {
|
|
400
|
+
if (isPresent) {
|
|
401
|
+
if (index === 0) {
|
|
402
|
+
errorMessage = `File '${configName}' takes precedences over`;
|
|
403
|
+
} else if (index === 1) {
|
|
404
|
+
errorMessage += ` '${configName}'${index === lastIndex ? "." : ""}`;
|
|
405
|
+
} else if (index !== lastIndex) {
|
|
406
|
+
errorMessage += `, '${configName}'`;
|
|
407
|
+
} else {
|
|
408
|
+
errorMessage += ` and '${configName}'.`;
|
|
409
|
+
}
|
|
410
|
+
index += 1;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
errorMessage += " You might want to remove one of those files.";
|
|
414
|
+
logger.warn(errorMessage);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/vite-plugins/override-components.ts
|
|
419
|
+
var virtualModuleId3 = "tutorialkit:override-components";
|
|
420
|
+
var resolvedId = `\0${virtualModuleId3}`;
|
|
421
|
+
function overrideComponents({ components, defaultRoutes }) {
|
|
422
|
+
return {
|
|
423
|
+
name: "tutorialkit-override-components-plugin",
|
|
424
|
+
resolveId(id) {
|
|
425
|
+
if (id === virtualModuleId3) {
|
|
426
|
+
return resolvedId;
|
|
427
|
+
}
|
|
428
|
+
return void 0;
|
|
429
|
+
},
|
|
430
|
+
async load(id) {
|
|
431
|
+
if (id === resolvedId) {
|
|
432
|
+
const topBar = components?.TopBar || resolveDefaultTopBar(defaultRoutes);
|
|
433
|
+
const headTags = components?.HeadTags || resolveDefaultHeadTags(defaultRoutes);
|
|
434
|
+
const dialog = components?.Dialog || "@tutorialkit-rb/react/dialog";
|
|
435
|
+
return `
|
|
436
|
+
export { default as TopBar } from '${topBar}';
|
|
437
|
+
export { default as Dialog } from '${dialog}';
|
|
438
|
+
export { default as HeadTags } from '${headTags}';
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
return void 0;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function resolveDefaultTopBar(defaultRoutes) {
|
|
446
|
+
if (defaultRoutes) {
|
|
447
|
+
return "@tutorialkit-rb/astro/default/components/TopBar.astro";
|
|
448
|
+
}
|
|
449
|
+
return "./src/components/TopBar.astro";
|
|
450
|
+
}
|
|
451
|
+
function resolveDefaultHeadTags(defaultRoutes) {
|
|
452
|
+
if (defaultRoutes) {
|
|
453
|
+
return "@tutorialkit-rb/astro/default/components/HeadTags.astro";
|
|
454
|
+
}
|
|
455
|
+
return "./src/components/HeadTags.astro";
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/vite-plugins/store.ts
|
|
459
|
+
import path5 from "node:path";
|
|
460
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
461
|
+
var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
|
|
462
|
+
var virtualModuleId4 = "tutorialkit:store";
|
|
463
|
+
var resolvedVirtualModuleId3 = `${virtualModuleId4}`;
|
|
464
|
+
var tutorialkitStore = {
|
|
465
|
+
name: "tutorialkit-store-virtual-mod-plugin",
|
|
466
|
+
resolveId(id) {
|
|
467
|
+
if (id === virtualModuleId4) {
|
|
468
|
+
return resolvedVirtualModuleId3;
|
|
469
|
+
}
|
|
470
|
+
return void 0;
|
|
471
|
+
},
|
|
472
|
+
async load(id) {
|
|
473
|
+
if (id === resolvedVirtualModuleId3) {
|
|
474
|
+
const pathToInit = normalizeImportPath(path5.join(__dirname2, "default/components/webcontainer.ts"));
|
|
475
|
+
return `
|
|
476
|
+
import { tutorialStore } from '${pathToInit}';
|
|
477
|
+
export default tutorialStore;
|
|
478
|
+
`;
|
|
479
|
+
}
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// src/webcontainer-files/index.ts
|
|
485
|
+
import fs4 from "node:fs";
|
|
486
|
+
import path8 from "node:path";
|
|
487
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
488
|
+
import { watch as watch2 } from "chokidar";
|
|
489
|
+
import { dim as dim2 } from "kleur/colors";
|
|
490
|
+
|
|
491
|
+
// src/webcontainer-files/cache.ts
|
|
492
|
+
import path7 from "node:path";
|
|
493
|
+
import { dim } from "kleur/colors";
|
|
494
|
+
|
|
495
|
+
// src/webcontainer-files/constants.ts
|
|
496
|
+
var IGNORED_FILES = ["**/.DS_Store", "**/*.swp", "**/node_modules/**"];
|
|
497
|
+
var EXTEND_CONFIG_FILEPATH = "/.tk-config.json";
|
|
498
|
+
var FILES_FOLDER_NAME = "_files";
|
|
499
|
+
var SOLUTION_FOLDER_NAME = "_solution";
|
|
500
|
+
|
|
501
|
+
// src/webcontainer-files/filesmap.ts
|
|
502
|
+
import fs3 from "node:fs";
|
|
503
|
+
import path6 from "node:path";
|
|
504
|
+
import glob from "fast-glob";
|
|
505
|
+
import { z } from "zod";
|
|
506
|
+
var configSchema = z.object({
|
|
507
|
+
extends: z.string()
|
|
508
|
+
}).strict();
|
|
509
|
+
var FilesMapGraph = class {
|
|
510
|
+
constructor(_nodes = /* @__PURE__ */ new Map()) {
|
|
511
|
+
this._nodes = _nodes;
|
|
512
|
+
}
|
|
513
|
+
getFilesMapByFolder(folder) {
|
|
514
|
+
try {
|
|
515
|
+
const resolvedPath = fs3.realpathSync(folder);
|
|
516
|
+
return this._nodes.get(resolvedPath);
|
|
517
|
+
} catch {
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
allFilesMap() {
|
|
522
|
+
return this._nodes.values();
|
|
523
|
+
}
|
|
524
|
+
updateFilesMapByFolder(folder, logger) {
|
|
525
|
+
const resolvedPath = fs3.realpathSync(folder);
|
|
526
|
+
let node = this._nodes.get(resolvedPath);
|
|
527
|
+
if (!node) {
|
|
528
|
+
node = new FilesMap(resolvedPath);
|
|
529
|
+
this._nodes.set(resolvedPath, node);
|
|
530
|
+
}
|
|
531
|
+
node.update(this._nodes, logger);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
var FilesMap = class _FilesMap {
|
|
535
|
+
/**
|
|
536
|
+
* Construct a new FileMap. To connect it to its graph, call
|
|
537
|
+
* `FileMap.initGraph` or `init(graph)`.
|
|
538
|
+
*
|
|
539
|
+
* @param path resolved path of this file map.
|
|
540
|
+
*/
|
|
541
|
+
constructor(path9) {
|
|
542
|
+
this.path = path9;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Initialize the graph of nodes by connecting them to one another.
|
|
546
|
+
* The graph is expected to be acyclic but this function won't throw any
|
|
547
|
+
* exception if it isn't.
|
|
548
|
+
*
|
|
549
|
+
* Instead when a `FilesMap` is turned into a JSON file, a warning will be
|
|
550
|
+
* printed if a cycle is found.
|
|
551
|
+
*
|
|
552
|
+
* @param folders initial folders found after a lookup.
|
|
553
|
+
*/
|
|
554
|
+
static async initGraph(folders, logger) {
|
|
555
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
556
|
+
const resolvedPaths = await Promise.all(folders.map((folder) => fs3.promises.realpath(folder)));
|
|
557
|
+
for (const resolvedPath of resolvedPaths) {
|
|
558
|
+
nodes.set(resolvedPath, new _FilesMap(resolvedPath));
|
|
559
|
+
}
|
|
560
|
+
for (const node of nodes.values()) {
|
|
561
|
+
node.update(nodes, logger);
|
|
562
|
+
}
|
|
563
|
+
return new FilesMapGraph(nodes);
|
|
564
|
+
}
|
|
565
|
+
_extend = null;
|
|
566
|
+
_dependents = /* @__PURE__ */ new Set();
|
|
567
|
+
update(graph, logger) {
|
|
568
|
+
const configPath = path6.join(this.path, EXTEND_CONFIG_FILEPATH);
|
|
569
|
+
if (!fs3.existsSync(configPath)) {
|
|
570
|
+
this.unlink();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
const jsonConfig = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
575
|
+
const config = configSchema.parse(jsonConfig);
|
|
576
|
+
const dependOnPath = fs3.realpathSync(path6.join(this.path, config.extends));
|
|
577
|
+
let dependOn = graph.get(dependOnPath);
|
|
578
|
+
if (!dependOn) {
|
|
579
|
+
dependOn = new _FilesMap(dependOnPath);
|
|
580
|
+
graph.set(dependOnPath, dependOn);
|
|
581
|
+
dependOn.update(graph, logger);
|
|
582
|
+
}
|
|
583
|
+
this.extend(dependOn);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
logger.warn(`Failed to parse '${configPath}', content won't be included.
|
|
586
|
+
Error: ${error}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
extend(other) {
|
|
590
|
+
this.unlink();
|
|
591
|
+
other._dependents.add(this);
|
|
592
|
+
this._extend = other;
|
|
593
|
+
}
|
|
594
|
+
unlink() {
|
|
595
|
+
this._extend?._dependents.delete(this);
|
|
596
|
+
this._extend = null;
|
|
597
|
+
}
|
|
598
|
+
*allDependents() {
|
|
599
|
+
for (const dependency of this._dependents) {
|
|
600
|
+
yield dependency;
|
|
601
|
+
yield* dependency.allDependents();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async toFiles(logger) {
|
|
605
|
+
const filePathsObj = await this._toFilePathsCheckCycles({
|
|
606
|
+
logger,
|
|
607
|
+
visitedNodes: []
|
|
608
|
+
});
|
|
609
|
+
delete filePathsObj[EXTEND_CONFIG_FILEPATH];
|
|
610
|
+
const filePaths = Object.entries(filePathsObj);
|
|
611
|
+
filePaths.sort();
|
|
612
|
+
const files = {};
|
|
613
|
+
for (const [webcontainerPath2, filePath] of filePaths) {
|
|
614
|
+
const buffer = fs3.readFileSync(filePath);
|
|
615
|
+
try {
|
|
616
|
+
const stringContent = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
617
|
+
files[webcontainerPath2] = stringContent;
|
|
618
|
+
} catch {
|
|
619
|
+
files[webcontainerPath2] = { base64: buffer.toString("base64") };
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return files;
|
|
623
|
+
}
|
|
624
|
+
async _toFilePathsCheckCycles(context) {
|
|
625
|
+
const seenIndex = context.visitedNodes.indexOf(this.path);
|
|
626
|
+
if (seenIndex !== -1) {
|
|
627
|
+
context.logger.warn(
|
|
628
|
+
[
|
|
629
|
+
"Cycle detected:",
|
|
630
|
+
...context.visitedNodes.map((filePath, index) => {
|
|
631
|
+
return ` * '${filePath}' ${index === seenIndex ? "<-- Cycle points back to that file" : ""}`;
|
|
632
|
+
}),
|
|
633
|
+
"Content will be ignored after that point."
|
|
634
|
+
].join("\n")
|
|
635
|
+
);
|
|
636
|
+
return {};
|
|
637
|
+
}
|
|
638
|
+
context.visitedNodes.push(this.path);
|
|
639
|
+
const filePaths = this._extend ? await this._extend._toFilePathsCheckCycles(context) : {};
|
|
640
|
+
await getAllFiles(this.path, filePaths);
|
|
641
|
+
return filePaths;
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
async function getAllFiles(dir, result) {
|
|
645
|
+
const filePaths = await glob(`${glob.convertPathToPattern(dir)}/**/*`, {
|
|
646
|
+
onlyFiles: true,
|
|
647
|
+
dot: true,
|
|
648
|
+
ignore: IGNORED_FILES
|
|
649
|
+
});
|
|
650
|
+
for (const filePath of filePaths) {
|
|
651
|
+
result[webcontainerPath(dir, filePath)] = filePath;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function webcontainerPath(dir, filePath) {
|
|
655
|
+
const result = `/${path6.relative(dir, filePath)}`;
|
|
656
|
+
if (path6.sep !== "/") {
|
|
657
|
+
return result.replaceAll(path6.sep, "/");
|
|
658
|
+
}
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/webcontainer-files/utils.ts
|
|
663
|
+
import { folderPathToFilesRef } from "@tutorialkit-rb/types";
|
|
664
|
+
import glob2 from "fast-glob";
|
|
665
|
+
function getFilesRef(pathToFolder, { contentDir, templatesDir }) {
|
|
666
|
+
if (pathToFolder.startsWith(contentDir)) {
|
|
667
|
+
pathToFolder = pathToFolder.slice(contentDir.length + 1);
|
|
668
|
+
} else if (pathToFolder.startsWith(templatesDir)) {
|
|
669
|
+
pathToFolder = "template" + pathToFolder.slice(templatesDir.length);
|
|
670
|
+
}
|
|
671
|
+
return folderPathToFilesRef(pathToFolder);
|
|
672
|
+
}
|
|
673
|
+
function getAllFilesMap({ contentDir, templatesDir }) {
|
|
674
|
+
return glob2(
|
|
675
|
+
[
|
|
676
|
+
`${glob2.convertPathToPattern(contentDir)}/**/${FILES_FOLDER_NAME}`,
|
|
677
|
+
`${glob2.convertPathToPattern(contentDir)}/**/${SOLUTION_FOLDER_NAME}`,
|
|
678
|
+
`${glob2.convertPathToPattern(templatesDir)}/*`
|
|
679
|
+
],
|
|
680
|
+
{ onlyDirectories: true, ignore: IGNORED_FILES }
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/webcontainer-files/cache.ts
|
|
685
|
+
var FilesMapCache = class {
|
|
686
|
+
constructor(_filesMapGraph, _logger, _server, _dirs) {
|
|
687
|
+
this._filesMapGraph = _filesMapGraph;
|
|
688
|
+
this._logger = _logger;
|
|
689
|
+
this._server = _server;
|
|
690
|
+
this._dirs = _dirs;
|
|
691
|
+
const { promise, resolve } = withResolvers();
|
|
692
|
+
this._readiness = promise;
|
|
693
|
+
this._resolve = resolve;
|
|
694
|
+
for (const filesMap of _filesMapGraph.allFilesMap()) {
|
|
695
|
+
this._requestsQueue.add(filesMap.path);
|
|
696
|
+
this._cache.set(getFilesRef(filesMap.path, this._dirs), void 0);
|
|
697
|
+
}
|
|
698
|
+
this._generateFileMaps();
|
|
699
|
+
}
|
|
700
|
+
// map of filesRef to file content
|
|
701
|
+
_cache = /* @__PURE__ */ new Map();
|
|
702
|
+
// files that will be generated
|
|
703
|
+
_requestsQueue = /* @__PURE__ */ new Set();
|
|
704
|
+
// a promise to wait on before serving a request for the end user
|
|
705
|
+
_readiness;
|
|
706
|
+
_resolve;
|
|
707
|
+
// this is to know which FileMaps are in use to decide whether or not the page should be reloaded
|
|
708
|
+
_hotPaths = /* @__PURE__ */ new Set();
|
|
709
|
+
_timeoutId = null;
|
|
710
|
+
generateFileMapForPath(filePath) {
|
|
711
|
+
const filesMapFolderPath = resolveFilesFolderPath(filePath, this._logger, this._dirs);
|
|
712
|
+
if (!filesMapFolderPath) {
|
|
713
|
+
this._logger.warn(`File ${filePath} is not part of the tutorial content or templates folders.`);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const filesRef = getFilesRef(filesMapFolderPath, this._dirs);
|
|
717
|
+
this._cache.set(filesRef, void 0);
|
|
718
|
+
this._invalidateCacheForDependencies(filesMapFolderPath);
|
|
719
|
+
this._requestsQueue.add(filesMapFolderPath);
|
|
720
|
+
if (this._timeoutId) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const { promise, resolve } = withResolvers();
|
|
724
|
+
this._readiness = promise;
|
|
725
|
+
this._resolve = resolve;
|
|
726
|
+
this._timeoutId = setTimeout(this._generateFileMaps, 100);
|
|
727
|
+
}
|
|
728
|
+
async canHandle(reqURL) {
|
|
729
|
+
const fileMapPath = new URL(reqURL ?? "/", "http://a").pathname.slice(1);
|
|
730
|
+
if (!this._cache.has(fileMapPath)) {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
let cacheValue = this._cache.get(fileMapPath);
|
|
734
|
+
if (typeof cacheValue === "undefined") {
|
|
735
|
+
await this._readiness;
|
|
736
|
+
cacheValue = this._cache.get(fileMapPath);
|
|
737
|
+
}
|
|
738
|
+
if (typeof cacheValue === "undefined") {
|
|
739
|
+
this._logger.error(`The cache never resolved for ${fileMapPath}.`);
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
this._hotPaths.add(fileMapPath);
|
|
743
|
+
return cacheValue;
|
|
744
|
+
}
|
|
745
|
+
_invalidateCacheForDependencies(folderPath) {
|
|
746
|
+
const filesMap = this._filesMapGraph.getFilesMapByFolder(folderPath);
|
|
747
|
+
if (!filesMap) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
for (const dependency of filesMap.allDependents()) {
|
|
751
|
+
this._cache.set(getFilesRef(dependency.path, this._dirs), void 0);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
_generateFileMaps = async () => {
|
|
755
|
+
const hotFilesRefs = [];
|
|
756
|
+
while (this._requestsQueue.size > 0) {
|
|
757
|
+
for (const folderPath of this._requestsQueue) {
|
|
758
|
+
this._filesMapGraph.updateFilesMapByFolder(folderPath, this._logger);
|
|
759
|
+
}
|
|
760
|
+
for (const folderPath of this._requestsQueue) {
|
|
761
|
+
for (const dependency of this._filesMapGraph.getFilesMapByFolder(folderPath).allDependents()) {
|
|
762
|
+
this._requestsQueue.add(dependency.path);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const requests = [...this._requestsQueue].map((folderPath) => {
|
|
766
|
+
return [getFilesRef(folderPath, this._dirs), this._filesMapGraph.getFilesMapByFolder(folderPath)];
|
|
767
|
+
});
|
|
768
|
+
this._requestsQueue.clear();
|
|
769
|
+
await Promise.all(
|
|
770
|
+
requests.map(async ([filesRef, filesMap]) => {
|
|
771
|
+
if (this._hotPaths.has(filesRef)) {
|
|
772
|
+
hotFilesRefs.push(filesRef);
|
|
773
|
+
}
|
|
774
|
+
const timeNow = performance.now();
|
|
775
|
+
const fileMap = await filesMap.toFiles(this._logger);
|
|
776
|
+
this._cache.set(filesRef, JSON.stringify(fileMap));
|
|
777
|
+
const elapsed = performance.now() - timeNow;
|
|
778
|
+
this._logger.info(`Generated ${filesRef} ${dim(Math.round(elapsed) + "ms")}`);
|
|
779
|
+
})
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
this._resolve();
|
|
783
|
+
if (hotFilesRefs.length > 0) {
|
|
784
|
+
this._hotPaths.clear();
|
|
785
|
+
this._server.hot.send({ type: "custom", event: "tk:refresh-wc-files", data: hotFilesRefs });
|
|
786
|
+
}
|
|
787
|
+
this._timeoutId = null;
|
|
788
|
+
};
|
|
789
|
+
};
|
|
790
|
+
function resolveFilesFolderPath(filePath, logger, { contentDir, templatesDir }) {
|
|
791
|
+
if (filePath.startsWith(templatesDir)) {
|
|
792
|
+
const index = filePath.indexOf(path7.sep, templatesDir.length + 1);
|
|
793
|
+
if (index === -1) {
|
|
794
|
+
logger.error(`Bug: ${filePath} is not in a directory under ${templatesDir}`);
|
|
795
|
+
return void 0;
|
|
796
|
+
}
|
|
797
|
+
return filePath.slice(0, index);
|
|
798
|
+
}
|
|
799
|
+
if (filePath.startsWith(contentDir)) {
|
|
800
|
+
let filesFolder = filePath;
|
|
801
|
+
while (filesFolder && !filesFolder.endsWith(FILES_FOLDER_NAME) && !filesFolder.endsWith(SOLUTION_FOLDER_NAME)) {
|
|
802
|
+
if (filesFolder === contentDir) {
|
|
803
|
+
logger.error(`Bug: ${filePath} was not under ${FILES_FOLDER_NAME} or ${SOLUTION_FOLDER_NAME}`);
|
|
804
|
+
return void 0;
|
|
805
|
+
}
|
|
806
|
+
filesFolder = path7.dirname(filesFolder);
|
|
807
|
+
}
|
|
808
|
+
return filesFolder;
|
|
809
|
+
}
|
|
810
|
+
return void 0;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/webcontainer-files/index.ts
|
|
814
|
+
var WebContainerFiles = class {
|
|
815
|
+
_watcher;
|
|
816
|
+
async serverSetup(projectRoot2, { server, logger }) {
|
|
817
|
+
const { contentDir, templatesDir } = this._folders(projectRoot2);
|
|
818
|
+
const graph = await FilesMap.initGraph(await getAllFilesMap({ contentDir, templatesDir }), logger);
|
|
819
|
+
const cache = new FilesMapCache(graph, logger, server, { contentDir, templatesDir });
|
|
820
|
+
this._watcher = watch2(
|
|
821
|
+
[
|
|
822
|
+
// TODO: does this work on Windows?
|
|
823
|
+
path8.join(contentDir, `**/${FILES_FOLDER_NAME}/**/*`),
|
|
824
|
+
path8.join(contentDir, `**/${SOLUTION_FOLDER_NAME}/**/*`),
|
|
825
|
+
templatesDir
|
|
826
|
+
],
|
|
827
|
+
{
|
|
828
|
+
ignored: IGNORED_FILES,
|
|
829
|
+
ignoreInitial: true
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
this._watcher.on("all", (eventName, filePath) => {
|
|
833
|
+
if (eventName === "addDir") {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
cache.generateFileMapForPath(filePath);
|
|
837
|
+
});
|
|
838
|
+
server.middlewares.use(async (req, res, next) => {
|
|
839
|
+
const result = await cache.canHandle(req.url);
|
|
840
|
+
if (!result) {
|
|
841
|
+
next();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
res.writeHead(200, {
|
|
845
|
+
"Content-Type": "application/json"
|
|
846
|
+
});
|
|
847
|
+
res.end(result);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
serverDone() {
|
|
851
|
+
return this._watcher?.close();
|
|
852
|
+
}
|
|
853
|
+
async buildAssets(projectRoot2, { dir, logger }) {
|
|
854
|
+
const { contentDir, templatesDir } = this._folders(projectRoot2);
|
|
855
|
+
const filesMapFolders = await getAllFilesMap({ contentDir, templatesDir });
|
|
856
|
+
const graph = await FilesMap.initGraph(filesMapFolders, logger);
|
|
857
|
+
await Promise.all(
|
|
858
|
+
filesMapFolders.map(async (folder) => {
|
|
859
|
+
folder = path8.normalize(folder);
|
|
860
|
+
const filesRef = getFilesRef(folder, { contentDir, templatesDir });
|
|
861
|
+
const dest = fileURLToPath3(new URL(filesRef, dir));
|
|
862
|
+
const fileMap = await graph.getFilesMapByFolder(folder).toFiles(logger);
|
|
863
|
+
await fs4.promises.writeFile(dest, JSON.stringify(fileMap));
|
|
864
|
+
logger.info(`${dim2(filesRef)}`);
|
|
865
|
+
})
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
_folders(projectRoot2) {
|
|
869
|
+
const contentDir = path8.join(projectRoot2, "./src/content/tutorial");
|
|
870
|
+
const templatesDir = path8.join(projectRoot2, "./src/templates");
|
|
871
|
+
return { contentDir, templatesDir };
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// src/index.ts
|
|
876
|
+
function createPlugin({
|
|
877
|
+
defaultRoutes = true,
|
|
878
|
+
components,
|
|
879
|
+
isolation,
|
|
880
|
+
enterprise,
|
|
881
|
+
expressiveCodePlugins = []
|
|
882
|
+
} = {}) {
|
|
883
|
+
const webcontainerFiles = new WebContainerFiles();
|
|
884
|
+
let _config;
|
|
885
|
+
return {
|
|
886
|
+
name: "@tutorialkit-rb/astro",
|
|
887
|
+
hooks: {
|
|
888
|
+
async "astro:config:setup"(options) {
|
|
889
|
+
const { injectRoute, updateConfig, config } = options;
|
|
890
|
+
updateConfig({
|
|
891
|
+
server: {
|
|
892
|
+
headers: {
|
|
893
|
+
"Cross-Origin-Embedder-Policy": isolation ?? "require-corp",
|
|
894
|
+
"Cross-Origin-Opener-Policy": "same-origin"
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
vite: {
|
|
898
|
+
optimizeDeps: {
|
|
899
|
+
entries: ["!**/src/(content|templates)/**"],
|
|
900
|
+
include: null ? [] : [
|
|
901
|
+
"@tutorialkit-rb/react",
|
|
902
|
+
/**
|
|
903
|
+
* The `picomatch` is CJS dependency used by `@tutorialkit-rb/runtime`.
|
|
904
|
+
* When used via `@tutorialkit-rb/astro`, it's a transitive dependency that's
|
|
905
|
+
* not automatically transformed.
|
|
906
|
+
*/
|
|
907
|
+
"@tutorialkit-rb/astro > picomatch/posix.js"
|
|
908
|
+
]
|
|
909
|
+
},
|
|
910
|
+
define: {
|
|
911
|
+
__ENTERPRISE__: `${!!enterprise}`,
|
|
912
|
+
__WC_CONFIG__: enterprise ? JSON.stringify(enterprise) : "undefined"
|
|
913
|
+
},
|
|
914
|
+
ssr: {
|
|
915
|
+
noExternal: ["@tutorialkit-rb/astro", "@tutorialkit-rb/react"]
|
|
916
|
+
},
|
|
917
|
+
plugins: [
|
|
918
|
+
userlandCSS,
|
|
919
|
+
tutorialkitStore,
|
|
920
|
+
tutorialkitCore,
|
|
921
|
+
overrideComponents({ components, defaultRoutes: !!defaultRoutes }),
|
|
922
|
+
null ? (await null).default() : null
|
|
923
|
+
]
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
updateMarkdownConfig(options);
|
|
927
|
+
if (defaultRoutes) {
|
|
928
|
+
if (defaultRoutes !== "tutorial-only") {
|
|
929
|
+
injectRoute({
|
|
930
|
+
pattern: "/",
|
|
931
|
+
entrypoint: "@tutorialkit-rb/astro/default/pages/index.astro",
|
|
932
|
+
prerender: true
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
injectRoute({
|
|
936
|
+
pattern: "[...slug]",
|
|
937
|
+
entrypoint: "@tutorialkit-rb/astro/default/pages/[...slug].astro",
|
|
938
|
+
prerender: true
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
const selfIndex = config.integrations.findIndex((integration) => integration.name === "@tutorialkit-rb/astro");
|
|
942
|
+
config.integrations.splice(
|
|
943
|
+
selfIndex + 1,
|
|
944
|
+
0,
|
|
945
|
+
...extraIntegrations({ root: fileURLToPath4(config.root), expressiveCodePlugins })
|
|
946
|
+
);
|
|
947
|
+
},
|
|
948
|
+
"astro:config:done"({ config }) {
|
|
949
|
+
_config = config;
|
|
950
|
+
},
|
|
951
|
+
async "astro:server:setup"(options) {
|
|
952
|
+
if (!_config) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const { server, logger } = options;
|
|
956
|
+
const projectRoot2 = fileURLToPath4(_config.root);
|
|
957
|
+
await webcontainerFiles.serverSetup(projectRoot2, options);
|
|
958
|
+
watchUserlandCSS(server, logger);
|
|
959
|
+
},
|
|
960
|
+
async "astro:server:done"() {
|
|
961
|
+
await webcontainerFiles.serverDone();
|
|
962
|
+
},
|
|
963
|
+
async "astro:build:done"(astroBuildDoneOptions) {
|
|
964
|
+
const projectRoot2 = fileURLToPath4(_config.root);
|
|
965
|
+
await webcontainerFiles.buildAssets(projectRoot2, astroBuildDoneOptions);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
export {
|
|
971
|
+
createPlugin as default
|
|
972
|
+
};
|