diff --git a/nx-dev/nx-dev/pages/tutorials.tsx b/nx-dev/nx-dev/pages/tutorials.tsx new file mode 100644 index 0000000000..4f014e7fa3 --- /dev/null +++ b/nx-dev/nx-dev/pages/tutorials.tsx @@ -0,0 +1,136 @@ +'use client'; +import { DefaultLayout, SectionHeading } from '@nx/nx-dev/ui-common'; +import { NextSeo } from 'next-seo'; +import { useRouter } from 'next/router'; +import { contactButton } from '../lib/components/headerCtaConfigs'; + +import { cx } from '@nx/nx-dev/ui-primitives'; +import { Framework, frameworkIcons } from '@nx/graph/legacy/icons'; + +export default function Tutorials(): JSX.Element { + const router = useRouter(); + + return ( + <> + + + + + + + Nx Tutorials + + + Get started with Nx by following along with one of these + tutorials. + + + + + + + + + + + + + + + + + + > + ); +} + +function TutorialCard({ + title, + type, + icon, + url, +}: { + title: string; + type: string; + icon: string; // Can be either a component name or a direct image URL + url: string; +}) { + return ( + + {icon && ( + + {icon.startsWith('/') ? ( + + ) : ( + frameworkIcons[icon as Framework]?.image + )} + + )} + + + {type} + + + {title} + + + + ); +} diff --git a/nx-dev/tutorial/astro.config.ts b/nx-dev/tutorial/astro.config.ts index b8d6cffe74..a04541a74b 100644 --- a/nx-dev/tutorial/astro.config.ts +++ b/nx-dev/tutorial/astro.config.ts @@ -1,6 +1,8 @@ import tutorialkit from '@tutorialkit/astro'; import { defineConfig, envField } from 'astro/config'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { runInTerminalPlugin } from './src/code-block-button/run-in-terminal-plugin'; +import { applyFileChangesPlugin } from './src/code-block-button/apply-file-changes-plugin'; export const config = defineConfig({ base: '/tutorials', @@ -45,6 +47,8 @@ export const config = defineConfig({ HeadTags: './src/components/HeadTags.astro', TopBar: './src/components/TopBar.astro', }, + defaultRoutes: 'tutorial-only', + expressiveCodePlugins: [runInTerminalPlugin(), applyFileChangesPlugin()], }), ], }); diff --git a/nx-dev/tutorial/compile-js-module.mjs b/nx-dev/tutorial/compile-js-module.mjs new file mode 100644 index 0000000000..e5595f91d4 --- /dev/null +++ b/nx-dev/tutorial/compile-js-module.mjs @@ -0,0 +1,21 @@ +import * as esbuild from 'esbuild'; +import { writeFileSync, readFileSync } from 'fs'; + +const output = esbuild + .transformSync(readFileSync('./src/code-block-button/js-module.ts'), { + loader: 'ts', + minify: true, + }) + .code.replaceAll('\n', '\\n'); + +writeFileSync( + './src/code-block-button/js-module.min.ts', + `/** + * GENERATED FILE - DO NOT EDIT + * This JS module code was built from the source file "js-module.ts". + * To change it, modify the source file and then re-run the build script. + */ + +export default '${output}'; +` +); diff --git a/nx-dev/tutorial/project.json b/nx-dev/tutorial/project.json index d40953bd56..48c6b40c2f 100644 --- a/nx-dev/tutorial/project.json +++ b/nx-dev/tutorial/project.json @@ -6,8 +6,21 @@ "tags": [], "// targets": "to see all targets run: nx show project tutorial --web", "targets": { + "build-code-block-button": { + "command": "node ./compile-js-module.mjs", + "inputs": [ + "{projectRoot}/src/code-block-button/compile-js-module.mjs", + "{projectRoot}/src/code-block-button/js-module.ts", + { "externalDependencies": ["esbuild"] } + ], + "outputs": ["{projectRoot}/src/code-block-button/js-module.min.ts"], + "options": { + "cwd": "{projectRoot}" + } + }, "build": { - "inputs": ["{projectRoot}/src/**/**"] + "inputs": ["{projectRoot}/src/**/**"], + "outputs": ["{projectRoot}/dist"] }, "lint": { "command": "echo no linting", diff --git a/nx-dev/tutorial/src/code-block-button/apply-file-changes-plugin.ts b/nx-dev/tutorial/src/code-block-button/apply-file-changes-plugin.ts new file mode 100644 index 0000000000..6ec76bdf4e --- /dev/null +++ b/nx-dev/tutorial/src/code-block-button/apply-file-changes-plugin.ts @@ -0,0 +1,30 @@ +import { PluginTexts, type ExpressiveCodePlugin } from '@expressive-code/core'; +import { pluginCodeBlockButton } from './base-plugin'; + +const svg = ``; + +export const applyFileChangesTexts = new PluginTexts({ + buttonTooltip: 'Apply file changes', + buttonExecuted: 'File updated...', +}); + +export function applyFileChangesPlugin(): ExpressiveCodePlugin { + return pluginCodeBlockButton( + 'applyFileChanges', + svg, + applyFileChangesTexts, + (codeBlock, isTerminal) => + !isTerminal && + ['solution:', 'file:'].some((prefix) => + codeBlock.metaOptions.getString('path')?.startsWith(prefix) + ), + (codeBlock, _) => { + return { + 'data-filepath': codeBlock.metaOptions + .getString('path') + .replace('solution:', '') + .replace('file:', ''), + }; + } + ); +} diff --git a/nx-dev/tutorial/src/code-block-button/base-plugin.ts b/nx-dev/tutorial/src/code-block-button/base-plugin.ts new file mode 100644 index 0000000000..d766d78c2a --- /dev/null +++ b/nx-dev/tutorial/src/code-block-button/base-plugin.ts @@ -0,0 +1,305 @@ +import type { + ExpressiveCodeBlock, + ExpressiveCodePlugin, + ResolverContext, +} from '@expressive-code/core'; +import { codeLineClass, PluginTexts } from '@expressive-code/core'; +import { h } from '@expressive-code/core/hast'; +import codeBlockButtonJsModule from './js-module.min'; + +const terminalLanguageGroups = [ + 'ansi', + 'bash', + 'bat', + 'batch', + 'cmd', + 'console', + 'nu', + 'nushell', + 'powershell', + 'ps', + 'ps1', + 'psd1', + 'psm1', + 'sh', + 'shell', + 'shellscript', + 'shellsession', + 'zsh', +]; + +export function isTerminalLanguage(language: string) { + return terminalLanguageGroups.includes(language); +} + +export const frameTypes = ['code', 'terminal', 'none', 'auto'] as const; +export type FrameType = (typeof frameTypes)[number]; + +export function getFramesBaseStyles( + name: string, + svg: string, + { cssVar }: ResolverContext +) { + const escapedSvg = svg.replace(//g, '%3E'); + const svgUrl = `url("data:image/svg+xml,${escapedSvg}")`; + + const buttonStyles = `.${name} { + display: flex; + gap: 0.25rem; + flex-direction: row; + position: absolute; + inset-block-start: calc(${cssVar('borderWidth')} + var(--button-spacing)); + inset-inline-end: calc(${cssVar('borderWidth')} + ${cssVar( + 'uiPaddingInline' + )} / 2 + var(--button-spacing)); + + /* hide code block button when there is no JavaScript */ + @media (scripting: none) { + display: none; + } + + /* RTL support: Code is always LTR, so the inline code block button + must match this to avoid overlapping the start of lines */ + direction: ltr; + unicode-bidi: isolate; + + button { + position: relative; + align-self: flex-end; + margin: 0; + padding: 0; + border: none; + border-radius: 0.2rem; + z-index: 1; + cursor: pointer; + + transition-property: opacity, background, border-color; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); + + /* Mobile-first styles: Make the button visible and tappable */ + width: 2.5rem; + height: 2.5rem; + background: var(--code-background); + opacity: 0.75; + + div { + position: absolute; + inset: 0; + border-radius: inherit; + + background: ${cssVar('frames.inlineButtonBackground')}; + opacity: ${cssVar('frames.inlineButtonBackgroundIdleOpacity')}; + + transition-property: inherit; + transition-duration: inherit; + transition-timing-function: inherit; + } + + &::before { + content: ''; + position: absolute; + pointer-events: none; + inset: 0; + border-radius: inherit; + border: ${cssVar('borderWidth')} solid ${cssVar( + 'frames.inlineButtonBorder' + )}; + opacity: ${cssVar('frames.inlineButtonBorderOpacity')}; + } + + &::after { + content: ''; + position: absolute; + pointer-events: none; + inset: 0; + background-color: ${cssVar('frames.inlineButtonForeground')}; + -webkit-mask-image: ${svgUrl}; + -webkit-mask-repeat: no-repeat; + mask-image: ${svgUrl}; + mask-repeat: no-repeat; + margin: 0.475rem; + line-height: 0; + } + + /* + On hover or focus, make the button fully opaque + and set hover/focus background opacity + */ + &:hover, &:focus:focus-visible { + opacity: 1; + div { + opacity: ${cssVar( + 'frames.inlineButtonBackgroundHoverOrFocusOpacity' + )}; + } + } + + /* On press, set active background opacity */ + &:active { + opacity: 1; + div { + opacity: ${cssVar( + 'frames.inlineButtonBackgroundActiveOpacity' + )}; + } + } + } + + .feedback { + --tooltip-arrow-size: 0.35rem; + --tooltip-bg: ${cssVar('frames.tooltipSuccessBackground')}; + color: ${cssVar('frames.tooltipSuccessForeground')}; + pointer-events: none; + user-select: none; + -webkit-user-select: none; + position: relative; + align-self: center; + background-color: var(--tooltip-bg); + z-index: 99; + padding: 0.125rem 0.75rem; + border-radius: 0.2rem; + margin-inline-end: var(--tooltip-arrow-size); + opacity: 0; + transition-property: opacity, transform; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transform: translate3d(0, 0.25rem, 0); + + &::after { + content: ''; + position: absolute; + pointer-events: none; + top: calc(50% - var(--tooltip-arrow-size)); + inset-inline-end: calc(-2 * (var(--tooltip-arrow-size) - 0.5px)); + border: var(--tooltip-arrow-size) solid transparent; + border-inline-start-color: var(--tooltip-bg); + } + + &.show { + opacity: 1; + transform: translate3d(0, 0, 0); + } + } + +} + +@media (hover: hover) { + /* If a mouse is available, hide the button by default and make it smaller */ + .${name} button { + opacity: 0; + width: 2rem; + height: 2rem; + } + + /* Reveal the non-hovered button in the following cases: + - when the frame is hovered + - when a sibling inside the frame is focused + - when the code block button shows a visible feedback message + */ + .frame:hover .${name} button:not(:hover), + .frame:focus-within :focus-visible ~ .${name} button:not(:hover), + .frame .${name} .feedback.show ~ button:not(:hover) { + opacity: 0.75; + } +} + +/* Increase end padding of the first line for the code block button */ +:nth-child(1 of .${codeLineClass}) .code { + padding-inline-end: calc(2rem + ${cssVar('codePaddingInline')}); +}`; + + return buttonStyles; +} + +export interface PluginFramesProps { + /** + * The code block's title. For terminal frames, this is displayed as the terminal window title, + * and for code frames, it's displayed as the file name in an open file tab. + * + * If no title is given, the plugin will try to automatically extract a title from a + * [file name comment](https://expressive-code.com/key-features/frames/#file-name-comments) + * inside your code, unless disabled by the `extractFileNameFromCode` option. + */ + title: string; + + /** + * Allows you to override the automatic frame type detection for a code block. + * + * The supported values are `code`, `terminal`, `none` and `auto`. + * + * @default `auto` + */ + frame: FrameType; +} + +export interface TextMap { + buttonTooltip: string; + buttonExecuted: string; +} + +export const defaultPluginCodeBlockButtonTexts = new PluginTexts({ + buttonTooltip: 'Run in terminal', + buttonExecuted: 'Command executing...', +}); + +const svg = [ + ``, + ``, + ``, +].join(''); + +export function pluginCodeBlockButton( + name: string = 'runInTerminal', + iconSvg: string = svg, + pluginCodeBlockButtonTexts: PluginTexts = defaultPluginCodeBlockButtonTexts, + shouldShowButton: ( + codeBlock: ExpressiveCodeBlock, + isTerminal: boolean + ) => boolean = () => true, + addAttributes: ( + codeBlock: ExpressiveCodeBlock, + isTerminal: boolean + ) => Record = () => ({}) +): ExpressiveCodePlugin { + return { + name, + baseStyles: (context: any) => getFramesBaseStyles(name, iconSvg, context), + jsModules: [ + codeBlockButtonJsModule + .replace('[SELECTOR]', `.expressive-code .${name} button`) + .replace('[BUTTON_NAME]', name), + ], + hooks: { + postprocessRenderedBlock: ({ codeBlock, renderData, locale }) => { + // get text strings for the current locale + const texts = pluginCodeBlockButtonTexts.get(locale); + + // retrieve information about the current block + const { frame = 'auto' } = codeBlock.props; + const isTerminal = + frame === 'terminal' || + (frame === 'auto' && isTerminalLanguage(codeBlock.language)); + + const extraElements: any[] = []; + + if (shouldShowButton(codeBlock, isTerminal)) { + extraElements.push( + h('div', { className: name }, [ + h( + 'button', + { + title: texts.buttonTooltip, + 'data-copied': texts.buttonExecuted, + ...addAttributes(codeBlock, isTerminal), + }, + [h('div')] + ), + ]) + ); + renderData.blockAst.children.push(...extraElements); + } + }, + }, + }; +} diff --git a/nx-dev/tutorial/src/code-block-button/js-module.min.ts b/nx-dev/tutorial/src/code-block-button/js-module.min.ts new file mode 100644 index 0000000000..e881f69809 --- /dev/null +++ b/nx-dev/tutorial/src/code-block-button/js-module.min.ts @@ -0,0 +1,7 @@ +/** + * GENERATED FILE - DO NOT EDIT + * This JS module code was built from the source file "js-module.ts". + * To change it, modify the source file and then re-run the build script. + */ + +export default '(function(){async function i(n){const t=n.currentTarget,o=t.dataset,c=o.code?.replace(/\u007f/g,`\n`),u=o.filepath;if(t.dispatchEvent(new CustomEvent("tutorialkit:[BUTTON_NAME]",{detail:{code:c,filepath:u},bubbles:!0})),t.parentNode?.querySelector(".feedback"))return;let e=document.createElement("div");e.classList.add("feedback"),e.append(o.copied),t.before(e),e.offsetWidth,requestAnimationFrame(()=>e?.classList.add("show"));const a=()=>!e||e.classList.remove("show"),d=()=>{!e||parseFloat(getComputedStyle(e).opacity)>0||(e.remove(),e=void 0)};setTimeout(a,1500),setTimeout(d,2500),t.addEventListener("blur",a),e.addEventListener("transitioncancel",d),e.addEventListener("transitionend",d)}const r="[SELECTOR]";function s(n){n.querySelectorAll?.(r).forEach(t=>t.addEventListener("click",i))}s(document),new MutationObserver(n=>n.forEach(t=>t.addedNodes.forEach(o=>{s(o)}))).observe(document.body,{childList:!0,subtree:!0}),document.addEventListener("astro:page-load",()=>{s(document)})})();\n'; diff --git a/nx-dev/tutorial/src/code-block-button/js-module.ts b/nx-dev/tutorial/src/code-block-button/js-module.ts new file mode 100644 index 0000000000..eb43d9ad46 --- /dev/null +++ b/nx-dev/tutorial/src/code-block-button/js-module.ts @@ -0,0 +1,91 @@ +// Compile this with `npx esbuild nx-dev/tutorial/src/code-block-button/js-module.ts --minify` + +(function () { + /** + * Handles clicks on a single copy button. + */ + async function clickHandler(event: Event) { + /* + * Attempt to perform the copy operation, first using the Clipboard API, + * and then falling back to a DOM-based approach + */ + const button = event.currentTarget as HTMLButtonElement; + const dataset = button.dataset as { + code: string; + copied: string; + filepath: string; + }; + const code = dataset.code?.replace(/\u007f/g, '\n'); + const filepath = dataset.filepath; + + button.dispatchEvent( + new CustomEvent('tutorialkit:[BUTTON_NAME]', { + detail: { code, filepath }, + bubbles: true, + }) + ); + + // Exit if the copy operation failed or there is already a tooltip present + if (button.parentNode?.querySelector('.feedback')) { + return; + } + + // Show feedback tooltip + let tooltip: HTMLDivElement | undefined = document.createElement('div'); + tooltip.classList.add('feedback'); + tooltip.append(dataset.copied); + button.before(tooltip); + + /* + * Use offsetWidth and requestAnimationFrame to opt out of DOM batching, + * which helps to ensure that the transition on 'show' works + */ + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + tooltip.offsetWidth; + requestAnimationFrame(() => tooltip?.classList.add('show')); + + // Hide & remove the tooltip again when we no longer need it + const hideTooltip = () => !tooltip || tooltip.classList.remove('show'); + const removeTooltip = () => { + if (!(!tooltip || parseFloat(getComputedStyle(tooltip).opacity) > 0)) { + tooltip.remove(); + tooltip = undefined; + } + }; + setTimeout(hideTooltip, 1500); + setTimeout(removeTooltip, 2500); + button.addEventListener('blur', hideTooltip); + tooltip.addEventListener('transitioncancel', removeTooltip); + tooltip.addEventListener('transitionend', removeTooltip); + } + + const SELECTOR = '[SELECTOR]'; + + /** + * Searches a node for matching buttons and initializes them + * unless the node does not support querySelectorAll (e.g. a text node). + */ + function initButtons(container: ParentNode | Document) { + container + .querySelectorAll?.(SELECTOR) + .forEach((btn) => btn.addEventListener('click', clickHandler)); + } + + // Use the function to initialize all buttons that exist right now + initButtons(document); + + // Register a MutationObserver to initialize any new buttons added later + const newButtonsObserver = new MutationObserver((mutations) => + mutations.forEach((mutation) => + mutation.addedNodes.forEach((node) => { + initButtons(node as ParentNode); + }) + ) + ); + newButtonsObserver.observe(document.body, { childList: true, subtree: true }); + + // Also re-initialize all buttons after view transitions initiated by popular frameworks + document.addEventListener('astro:page-load', () => { + initButtons(document); + }); +})(); diff --git a/nx-dev/tutorial/src/code-block-button/run-in-terminal-plugin.ts b/nx-dev/tutorial/src/code-block-button/run-in-terminal-plugin.ts new file mode 100644 index 0000000000..88fa96b383 --- /dev/null +++ b/nx-dev/tutorial/src/code-block-button/run-in-terminal-plugin.ts @@ -0,0 +1,36 @@ +import { PluginTexts, type ExpressiveCodePlugin } from '@expressive-code/core'; +import { pluginCodeBlockButton } from './base-plugin'; + +const svg = [ + ``, + ``, + ``, +].join(''); + +export const runInTerminalTexts = new PluginTexts({ + buttonTooltip: 'Run in terminal', + buttonExecuted: 'Command executing...', +}); + +export function runInTerminalPlugin(): ExpressiveCodePlugin { + return pluginCodeBlockButton( + 'runInTerminal', + svg, + runInTerminalTexts, + (_, isTerminal) => isTerminal, + (codeBlock, _) => { + // remove comment lines starting with `#` from terminal frames + let code = codeBlock.code.replace(/(?<=^|\n)\s*#.*($|\n+)/g, '').trim(); + + /** + * Replace all line breaks with a special character + * because HAST does not encode them in attribute values + * (which seems to work, but looks ugly in the HTML source) + */ + code = code.replace(/\n/g, '\u007f'); + return { + 'data-code': code, + }; + } + ); +} diff --git a/nx-dev/tutorial/src/components/GlobalCustomizations.tsx b/nx-dev/tutorial/src/components/GlobalCustomizations.tsx new file mode 100644 index 0000000000..59e2142483 --- /dev/null +++ b/nx-dev/tutorial/src/components/GlobalCustomizations.tsx @@ -0,0 +1,103 @@ +'use client'; +import { useEffect } from 'react'; +import { webcontainer } from 'tutorialkit:core'; +import tutorialStore from 'tutorialkit:store'; + +export function GlobalCustomizations() { + useEffect(() => { + // These actions run on every page load + + // Disable previous and next buttons if this is the first or last lesson of a tutorial + function waitForTopBar() { + if (!document.querySelector('#top-bar')) { + setTimeout(waitForTopBar, 100); + } else { + if (document.querySelector('#top-bar.first-lesson')) { + const [topPrevButton, bottomPrevButton] = document + .querySelectorAll('[class*=i-ph-arrow-left]') + .values() + .map((el) => el.parentElement); + topPrevButton.classList.add('opacity-32', 'pointer-events-none'); + topPrevButton.setAttribute('aria-disabled', 'true'); + topPrevButton.removeAttribute('href'); + bottomPrevButton?.remove(); + } + if (document.querySelector('#top-bar.last-lesson')) { + const [topNextButton, bottomNextButton] = document + .querySelectorAll('[class*=i-ph-arrow-right]') + .values() + .map((el) => el.parentElement); + topNextButton.classList.add('opacity-32', 'pointer-events-none'); + topNextButton.setAttribute('aria-disabled', 'true'); + topNextButton.removeAttribute('href'); + bottomNextButton?.remove(); + } + } + } + waitForTopBar(); + + webcontainer.then(async (wc) => { + // Stub out git command + await wc.fs.writeFile( + 'git', + 'echo "Git is not available in a WebContainer"' + ); + const terminal = tutorialStore.terminalConfig.get().panels[0]?.terminal; + if (!terminal) { + return; + } + terminal?.input('echo "hi"\n'); + function callOnce(fn: Function) { + let called = false; + return function () { + if (!called) { + called = true; + fn(); + } + }; + } + (terminal as any).onLineFeed( + callOnce(() => { + setTimeout(() => { + terminal.input('export PATH="$PATH:/home/tutorial"\n'); + setTimeout(() => { + terminal.input('clear\n'); + }, 10); + }, 10); + }) + ); + }); + + // Run these actions only once + const tempData: any = window; + if (tempData.globalCustomizationsInitialized) { + return; + } + tempData.globalCustomizationsInitialized = true; + + // Apply file changes + async function applyFileChanges(e: any) { + const { filepath } = e.detail; + if (!filepath) { + return; + } + tutorialStore.updateFile( + filepath, + (tutorialStore as any)._lessonSolution[filepath] + ); + tutorialStore.setSelectedFile(filepath); + } + document.addEventListener('tutorialkit:applyFileChanges', applyFileChanges); + + // Run code in terminal when tutorialkit:runInTerminal event is triggered + function runInTerminal(e: any) { + if (tutorialStore.hasTerminalPanel()) { + tutorialStore.terminalConfig + .get() + .panels[0].terminal?.input(e.detail.code + '\n'); + } + } + document.addEventListener('tutorialkit:runInTerminal', runInTerminal); + }, []); + return null; +} diff --git a/nx-dev/tutorial/src/components/TopBar.astro b/nx-dev/tutorial/src/components/TopBar.astro index eada4b141f..23f6f5ede8 100644 --- a/nx-dev/tutorial/src/components/TopBar.astro +++ b/nx-dev/tutorial/src/components/TopBar.astro @@ -1,9 +1,16 @@ --- +import { GlobalCustomizations } from "./GlobalCustomizations"; import { FrontendObservability } from "./Observability"; +import { getCollection } from 'astro:content'; +const entries = await getCollection('tutorial', ({ slug }) => { + return slug.startsWith(Astro.url.pathname.replace('/tutorials/', '')); +}); +const customMeta = (entries[0]?.data as any)?.custom; +const addedClass = [customMeta?.first && 'first-lesson', customMeta?.last && 'last-lesson', ' '].filter(Boolean).join(' '); --- - + diff --git a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/1-introduction/1-welcome/content.mdx b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/1-introduction/1-welcome/content.mdx index 63f2f877bc..f5e89059e0 100644 --- a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/1-introduction/1-welcome/content.mdx +++ b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/1-introduction/1-welcome/content.mdx @@ -2,6 +2,8 @@ type: lesson title: Starting Repository focus: /package.json +custom: + first: true --- # TypeScript Monorepo Tutorial diff --git a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/4-task-pipelines/content.md b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/4-task-pipelines/content.md index f34d7e7f3b..6d43c748e3 100644 --- a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/4-task-pipelines/content.md +++ b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/4-task-pipelines/content.md @@ -10,24 +10,8 @@ focus: /nx.json You may have noticed in the `packages/zoo/package.json` file, there is a `serve` script that expects the `build` task to already have created the `dist` folder. Let's set up a task pipeline that will guarantee that the project's `build` task has been run. -```json title="nx.json" {4-6} -{ - "$schema": "./node_modules/nx/schemas/nx-schema.json", - "targetDefaults": { - "serve": { - "dependsOn": ["build"] - }, - "build": { - "dependsOn": ["^build"], - "outputs": ["{projectRoot}/dist"], - "cache": true - }, - "typecheck": { - "cache": true - } - }, - "defaultBase": "main" -} +```solution:/nx.json title="nx.json" {4-6} collapse={2,7-14} + ``` The `serve` target's `dependsOn` line makes Nx run the `build` task for the current project before running the current project's `build` task. diff --git a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/6-sync-ts-references/content.mdx b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/6-sync-ts-references/content.mdx index 042b6c54f5..3518026c48 100644 --- a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/6-sync-ts-references/content.mdx +++ b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/2-smart-monorepo/6-sync-ts-references/content.mdx @@ -26,19 +26,19 @@ Set the bundler to `tsc`, the linter to `none` and the unit test runner to `none Now we can move the `getRandomItem` function from `packages/names/names.ts` and `packages/animals/animals.ts` into the `packages/util/src/lib/util.ts` file. -```ts title="packages/util/src/lib/util.ts" +```solution:/packages/util/src/lib/util.ts title="packages/util/src/lib/util.ts" export function getRandomItem(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } ``` -```ts title="packages/animals/animals.ts" +```solution:/packages/animals/animals.ts title="packages/animals/animals.ts" collapse={3-150} import { getRandomItem } from '@tuskdesign/util'; // ... ``` -```ts title="packages/names/names.ts" +```solution:/packages/names/names.ts title="packages/names/names.ts" collapse={3-150} import { getRandomItem } from '@tuskdesign/util'; // ... diff --git a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/4-fast-ci/4-open-pr/content.md b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/4-fast-ci/4-open-pr/content.md index 0544b3bbe9..637c4dfd3e 100644 --- a/nx-dev/tutorial/src/content/tutorial/1-ts-packages/4-fast-ci/4-open-pr/content.md +++ b/nx-dev/tutorial/src/content/tutorial/1-ts-packages/4-fast-ci/4-open-pr/content.md @@ -1,6 +1,8 @@ --- type: lesson title: Open a Pull Request +custom: + last: true --- ## Open a Pull Request diff --git a/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/1r-introduction/1-welcome/content.mdx b/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/1r-introduction/1-welcome/content.mdx index 3f9df8ef34..29bb4c0e6e 100644 --- a/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/1r-introduction/1-welcome/content.mdx +++ b/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/1r-introduction/1-welcome/content.mdx @@ -1,6 +1,8 @@ --- type: lesson title: Why Use a Monorepo? +custom: + first: true --- # React Monorepo Tutorial diff --git a/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/2r-smart-monorepo/4-inferred-tasks/content.mdx b/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/2r-smart-monorepo/4-inferred-tasks/content.mdx index 324710343d..4be8f3a5ab 100644 --- a/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/2r-smart-monorepo/4-inferred-tasks/content.mdx +++ b/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/2r-smart-monorepo/4-inferred-tasks/content.mdx @@ -23,14 +23,8 @@ npx nx show project @react-monorepo/react-store If you expand the `build` task, you can see that it was created by the `@nx/vite` plugin by analyzing your `vite.config.ts` file. Notice the outputs are defined as `{projectRoot}/dist`. This value is being read from the `build.outDir` defined in your `vite.config.ts` file. Let's change that value in your `vite.config.ts` file: -```ts title="apps/react-store/vite.config.ts" -export default defineConfig({ - // ... - build: { - outDir: './build', - // ... - }, -}); +```solution:/apps/react-store/vite.config.ts title="apps/react-store/vite.config.ts" collapse={1-6,8-23,26-30,32-42} + ``` Now if you close and reopen the project details view, the outputs for the build target will say `{projectRoot}/build`. This feature ensures that Nx will always cache the correct files. diff --git a/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/3r-fast-ci/4-open-pr/content.md b/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/3r-fast-ci/4-open-pr/content.md index 0544b3bbe9..637c4dfd3e 100644 --- a/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/3r-fast-ci/4-open-pr/content.md +++ b/nx-dev/tutorial/src/content/tutorial/2-react-monorepo/3r-fast-ci/4-open-pr/content.md @@ -1,6 +1,8 @@ --- type: lesson title: Open a Pull Request +custom: + last: true --- ## Open a Pull Request diff --git a/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/1a-introduction/1-welcome/content.mdx b/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/1a-introduction/1-welcome/content.mdx index 15729b5948..ccf08cf7ef 100644 --- a/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/1a-introduction/1-welcome/content.mdx +++ b/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/1a-introduction/1-welcome/content.mdx @@ -1,6 +1,8 @@ --- type: lesson title: Why Use a Monorepo? +custom: + first: true --- # Angular Monorepo Tutorial diff --git a/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/3a-fast-ci/4-open-pr/content.md b/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/3a-fast-ci/4-open-pr/content.md index 0544b3bbe9..637c4dfd3e 100644 --- a/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/3a-fast-ci/4-open-pr/content.md +++ b/nx-dev/tutorial/src/content/tutorial/3-angular-monorepo/3a-fast-ci/4-open-pr/content.md @@ -1,6 +1,8 @@ --- type: lesson title: Open a Pull Request +custom: + last: true --- ## Open a Pull Request diff --git a/nx-dev/tutorial/src/content/tutorial/meta.md b/nx-dev/tutorial/src/content/tutorial/meta.md index 7e2e1ea889..842fe0cbe4 100644 --- a/nx-dev/tutorial/src/content/tutorial/meta.md +++ b/nx-dev/tutorial/src/content/tutorial/meta.md @@ -9,5 +9,6 @@ i18n: partTemplate: 'Tutorial: ${title}' downloadAsZip: true prepareCommands: + - ['chmod +x git', 'Stubbing git'] - ['npm install', 'Installing dependencies'] --- diff --git a/nx-dev/ui-markdoc/src/lib/nodes/helpers/transform-image-path.ts b/nx-dev/ui-markdoc/src/lib/nodes/helpers/transform-image-path.ts index 3bc0ce0af4..353c973159 100644 --- a/nx-dev/ui-markdoc/src/lib/nodes/helpers/transform-image-path.ts +++ b/nx-dev/ui-markdoc/src/lib/nodes/helpers/transform-image-path.ts @@ -1,4 +1,3 @@ -import { join } from 'path'; import { uriTransformer } from './uri-transformer'; export function transformImagePath( @@ -13,7 +12,14 @@ export function transformImagePath( if (isRelative) { return uriTransformer( - join('/', documentFilePath.split('/').splice(3).join('/'), '..', src) + new URL( + [ + 'http://example.com', + documentFilePath.split('/').splice(3).join('/'), + '..', + src, + ].join('/') + ).pathname ); } diff --git a/package.json b/package.json index 598f3e49e3..f633506482 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@eslint/compat": "^1.1.1", "@eslint/eslintrc": "^2.1.1", "@eslint/js": "^8.48.0", + "@expressive-code/core": "0.35.6", "@floating-ui/react": "0.26.6", "@iconify-json/ph": "^1.1.12", "@iconify-json/svg-spinners": "^1.1.2", @@ -130,10 +131,10 @@ "@swc/helpers": "0.5.11", "@swc/jest": "0.2.36", "@testing-library/react": "15.0.6", - "@tutorialkit/astro": "1.3.1", - "@tutorialkit/react": "1.3.1", - "@tutorialkit/theme": "1.3.1", - "@tutorialkit/types": "1.3.1", + "@tutorialkit/astro": "1.5.0", + "@tutorialkit/react": "1.5.0", + "@tutorialkit/theme": "1.5.0", + "@tutorialkit/types": "1.5.0", "@types/cytoscape": "^3.18.2", "@types/detect-port": "^1.3.2", "@types/ejs": "3.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92ef571584..b5e997cd9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,6 +244,9 @@ importers: '@eslint/js': specifier: ^8.48.0 version: 8.57.1 + '@expressive-code/core': + specifier: 0.35.6 + version: 0.35.6 '@floating-ui/react': specifier: 0.26.6 version: 0.26.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -482,17 +485,17 @@ importers: specifier: 15.0.6 version: 15.0.6(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tutorialkit/astro': - specifier: 1.3.1 - version: 1.3.1(@types/node@20.16.10)(@types/react-dom@18.3.0)(astro@4.15.0(@types/node@20.16.10)(less@4.1.3)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(typescript@5.7.3))(less@4.1.3)(postcss@8.4.38)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + specifier: 1.5.0 + version: 1.5.0(@types/node@20.16.10)(@types/react-dom@18.3.0)(astro@4.15.0(@types/node@20.16.10)(less@4.1.3)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(typescript@5.7.3))(less@4.1.3)(postcss@8.4.38)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) '@tutorialkit/react': - specifier: 1.3.1 - version: 1.3.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + specifier: 1.5.0 + version: 1.5.0(@types/react-dom@18.3.0)(@types/react@18.3.1)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) '@tutorialkit/theme': - specifier: 1.3.1 - version: 1.3.1(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + specifier: 1.5.0 + version: 1.5.0(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) '@tutorialkit/types': - specifier: 1.3.1 - version: 1.3.1 + specifier: 1.5.0 + version: 1.5.0 '@types/cytoscape': specifier: ^3.18.2 version: 3.21.8 @@ -8029,22 +8032,22 @@ packages: resolution: {integrity: sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==} engines: {node: ^18.17.0 || >=20.5.0} - '@tutorialkit/astro@1.3.1': - resolution: {integrity: sha512-1K+G32ZMLk+KgvIKFNIKYN3IzR7kWEn3X6ZOIO9yH6hr7BCIcxyVXDOH20w6F+RR0lFH2ylY3CCr5PxYHjsWKw==} + '@tutorialkit/astro@1.5.0': + resolution: {integrity: sha512-XFCCZIJkzsj2dOiLqQfrGSaUGGRMxSjqSMl7GO4de3k7Dkq55oFddQwzBdSFh6Zg1SSQ13QN6G8+cwVDdBNTiw==} peerDependencies: astro: ^4.15.0 - '@tutorialkit/react@1.3.1': - resolution: {integrity: sha512-9DoSbZbHwsqAoPNf/v6o1jEJT6/CL/UptIUw4fxqBMEg33R1Uq704uM9ySOQL0HtA/fGfFdUnJGh4KniSe+fBw==} + '@tutorialkit/react@1.5.0': + resolution: {integrity: sha512-+fcDXxbK6RO0Gf9DqodWPcVTJPkPwnKbaS+ZQdaFLdVcxWQ237r0MbK/srL7EJrldITvGu6no+KbMS3E/GhGIg==} - '@tutorialkit/runtime@1.3.1': - resolution: {integrity: sha512-ou/GPeQpuSRI4i/M9dmsj07dOJGeZZB6ETbAQMwVWsTOhYCZ4qUk3blmrnkyKl2gHC9hYdProIKa5OkHBE37cg==} + '@tutorialkit/runtime@1.5.0': + resolution: {integrity: sha512-pVl2B4QAozAtIO7PCSUDPbSSSBUk2XnVRcafYnRWSRjbnWAxgnVQ+ZHSxl3G9acWhxujjLZjsYgVRQci3eIWVg==} - '@tutorialkit/theme@1.3.1': - resolution: {integrity: sha512-VTzDAPt3Hj9E3NfxnhOUshCWoAB4uMRP2AhvrqXLMQkdEluJ2GkXOreEzS8WWcsPzr4jqyFFj39wYwBBRuqd1w==} + '@tutorialkit/theme@1.5.0': + resolution: {integrity: sha512-EE4vrbuXaHZ0B4qcwKe9rdUiDja8wgxJScKvJHdNVDlWEusCmcmbhH2yj782H1Y8dGJ7kS32Qki6w4ycu115Ww==} - '@tutorialkit/types@1.3.1': - resolution: {integrity: sha512-4V7Jxsmeyq2a6UZBYTkNSl3NG75SZvFG/pUuxjtZVO1/EVY93Pz1f1RqNhuzlQf1d/rEOYlREMU25GWOij4z2Q==} + '@tutorialkit/types@1.5.0': + resolution: {integrity: sha512-+XnpaMwCLgqWyK29jopK/XGTvyEP6K3CoZuNa9eDk6HFndWawsm5ENrH7/f04kPfkhkbxaccSCFzykxQAneT2g==} '@tweenjs/tween.js@23.1.3': resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} @@ -29126,7 +29129,7 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 - '@tutorialkit/astro@1.3.1(@types/node@20.16.10)(@types/react-dom@18.3.0)(astro@4.15.0(@types/node@20.16.10)(less@4.1.3)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(typescript@5.7.3))(less@4.1.3)(postcss@8.4.38)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': + '@tutorialkit/astro@1.5.0(@types/node@20.16.10)(@types/react-dom@18.3.0)(astro@4.15.0(@types/node@20.16.10)(less@4.1.3)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(typescript@5.7.3))(less@4.1.3)(postcss@8.4.38)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': dependencies: '@astrojs/mdx': 3.1.9(astro@4.15.0(@types/node@20.16.10)(less@4.1.3)(rollup@4.22.0)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(typescript@5.7.3)) '@astrojs/react': 3.6.3(@types/node@20.16.10)(@types/react-dom@18.3.0)(@types/react@18.3.20)(less@4.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0) @@ -29134,10 +29137,10 @@ snapshots: '@expressive-code/plugin-line-numbers': 0.35.6 '@nanostores/react': 0.7.2(nanostores@0.10.3)(react@18.3.1) '@stackblitz/sdk': 1.11.0 - '@tutorialkit/react': 1.3.1(@types/react-dom@18.3.0)(@types/react@18.3.20)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) - '@tutorialkit/runtime': 1.3.1 - '@tutorialkit/theme': 1.3.1(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) - '@tutorialkit/types': 1.3.1 + '@tutorialkit/react': 1.5.0(@types/react-dom@18.3.0)(@types/react@18.3.20)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + '@tutorialkit/runtime': 1.5.0 + '@tutorialkit/theme': 1.5.0(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + '@tutorialkit/types': 1.5.0 '@types/react': 18.3.20 '@unocss/reset': 0.62.4 '@webcontainer/api': 1.5.1 @@ -29175,7 +29178,7 @@ snapshots: - terser - vite - '@tutorialkit/react@1.3.1(@types/react-dom@18.3.0)(@types/react@18.3.1)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': + '@tutorialkit/react@1.5.0(@types/react-dom@18.3.0)(@types/react@18.3.1)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': dependencies: '@codemirror/autocomplete': 6.18.6 '@codemirror/commands': 6.8.1 @@ -29199,8 +29202,8 @@ snapshots: '@radix-ui/react-context-menu': 2.2.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.18.6)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.3)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.21)(@lezer/lr@1.4.2) - '@tutorialkit/runtime': 1.3.1 - '@tutorialkit/theme': 1.3.1(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + '@tutorialkit/runtime': 1.5.0 + '@tutorialkit/theme': 1.5.0(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) '@webcontainer/api': 1.5.1 '@xterm/addon-fit': 0.10.0(@xterm/xterm@5.5.0) '@xterm/addon-web-links': 0.11.0(@xterm/xterm@5.5.0) @@ -29222,7 +29225,7 @@ snapshots: - supports-color - vite - '@tutorialkit/react@1.3.1(@types/react-dom@18.3.0)(@types/react@18.3.20)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': + '@tutorialkit/react@1.5.0(@types/react-dom@18.3.0)(@types/react@18.3.20)(postcss@8.4.38)(react-dom@18.3.1(react@18.3.1))(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': dependencies: '@codemirror/autocomplete': 6.18.6 '@codemirror/commands': 6.8.1 @@ -29246,8 +29249,8 @@ snapshots: '@radix-ui/react-context-menu': 2.2.7(@types/react-dom@18.3.0)(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@replit/codemirror-lang-svelte': 6.0.0(@codemirror/autocomplete@6.18.6)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.9)(@codemirror/lang-javascript@6.2.3)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/javascript@1.4.21)(@lezer/lr@1.4.2) - '@tutorialkit/runtime': 1.3.1 - '@tutorialkit/theme': 1.3.1(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) + '@tutorialkit/runtime': 1.5.0 + '@tutorialkit/theme': 1.5.0(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) '@webcontainer/api': 1.5.1 '@xterm/addon-fit': 0.10.0(@xterm/xterm@5.5.0) '@xterm/addon-web-links': 0.11.0(@xterm/xterm@5.5.0) @@ -29269,14 +29272,14 @@ snapshots: - supports-color - vite - '@tutorialkit/runtime@1.3.1': + '@tutorialkit/runtime@1.5.0': dependencies: - '@tutorialkit/types': 1.3.1 + '@tutorialkit/types': 1.5.0 '@webcontainer/api': 1.5.1 nanostores: 0.10.3 picomatch: 4.0.2 - '@tutorialkit/theme@1.3.1(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': + '@tutorialkit/theme@1.5.0(postcss@8.4.38)(rollup@4.22.0)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))': dependencies: '@iconify-json/ph': 1.2.2 '@iconify-json/svg-spinners': 1.2.2 @@ -29289,7 +29292,7 @@ snapshots: - supports-color - vite - '@tutorialkit/types@1.3.1': + '@tutorialkit/types@1.5.0': dependencies: zod: 3.23.8 @@ -38619,7 +38622,7 @@ snapshots: mlly@1.7.4: dependencies: acorn: 8.14.0 - pathe: 2.0.2 + pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.5.4 @@ -39857,7 +39860,7 @@ snapshots: dependencies: confbox: 0.1.8 mlly: 1.7.4 - pathe: 2.0.2 + pathe: 2.0.3 pkg-types@2.1.0: dependencies: