From fc8756c8c6910a430c6e20fc52ede4d3408a5ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Mon, 5 Aug 2024 19:37:25 +0300 Subject: [PATCH 1/7] feat: Add 'Open in StackBlitz'-button --- .../content/docs/reference/configuration.mdx | 3 + packages/astro/package.json | 1 + .../components/OpenInStackblitzLink.astro | 91 +++++++++++++++++++ .../astro/src/default/components/TopBar.astro | 3 + .../default/components/TopBarWrapper.astro | 6 +- .../astro/src/default/pages/[...slug].astro | 2 +- packages/astro/src/default/utils/content.ts | 2 + packages/cli/src/commands/eject/index.ts | 9 +- .../create-tutorial.test.ts.snap | 1 + packages/runtime/src/store/index.ts | 4 + .../tutorial/2-advanced/1-unicorn/meta.md | 1 + packages/types/src/schemas/common.ts | 1 + pnpm-lock.yaml | 7 ++ 13 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 packages/astro/src/default/components/OpenInStackblitzLink.astro diff --git a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx index a084ea0c1..53474606b 100644 --- a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx +++ b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx @@ -211,3 +211,6 @@ You can instruct Github to show the source code instead by adding `plain=1` quer ::: +### `openInStackBlitzLink` +Display a link for opening current lesson in StackBlitz. + diff --git a/packages/astro/package.json b/packages/astro/package.json index 6958c13c4..042dbf5df 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -35,6 +35,7 @@ "@expressive-code/plugin-collapsible-sections": "^0.35.3", "@expressive-code/plugin-line-numbers": "^0.35.3", "@nanostores/react": "0.7.2", + "@stackblitz/sdk": "^1.11.0", "@tutorialkit/components-react": "workspace:*", "@tutorialkit/runtime": "workspace:*", "@tutorialkit/theme": "workspace:*", diff --git a/packages/astro/src/default/components/OpenInStackblitzLink.astro b/packages/astro/src/default/components/OpenInStackblitzLink.astro new file mode 100644 index 000000000..e740b6746 --- /dev/null +++ b/packages/astro/src/default/components/OpenInStackblitzLink.astro @@ -0,0 +1,91 @@ + + + diff --git a/packages/astro/src/default/components/TopBar.astro b/packages/astro/src/default/components/TopBar.astro index 92626ae62..e480347b1 100644 --- a/packages/astro/src/default/components/TopBar.astro +++ b/packages/astro/src/default/components/TopBar.astro @@ -4,6 +4,9 @@
+
+ +
diff --git a/packages/astro/src/default/components/TopBarWrapper.astro b/packages/astro/src/default/components/TopBarWrapper.astro index bcfca1ea3..3bc5a625c 100644 --- a/packages/astro/src/default/components/TopBarWrapper.astro +++ b/packages/astro/src/default/components/TopBarWrapper.astro @@ -2,19 +2,23 @@ import { TopBar } from 'tutorialkit:override-components'; import { ThemeSwitch } from './ThemeSwitch'; import { LoginButton } from './LoginButton'; +import OpenInStackblitzLink from './OpenInStackblitzLink.astro'; import Logo from './Logo.astro'; import { useAuth } from './setup'; interface Props { logoLink: string; + openInStackBlitzLink: boolean | undefined; } -const { logoLink } = Astro.props; +const { logoLink, openInStackBlitzLink: showOpenInStackBlitzLink } = Astro.props; --- + {showOpenInStackBlitzLink && } + {useAuth && } diff --git a/packages/astro/src/default/pages/[...slug].astro b/packages/astro/src/default/pages/[...slug].astro index b670853ea..cd3fd20db 100644 --- a/packages/astro/src/default/pages/[...slug].astro +++ b/packages/astro/src/default/pages/[...slug].astro @@ -21,7 +21,7 @@ const { lesson, logoLink, navList, title } = Astro.props as Props;
- +
diff --git a/packages/astro/src/default/utils/content.ts b/packages/astro/src/default/utils/content.ts index ea8aaa4bb..d0e0e7b41 100644 --- a/packages/astro/src/default/utils/content.ts +++ b/packages/astro/src/default/utils/content.ts @@ -41,6 +41,7 @@ export async function getTutorial(): Promise { // default template if not specified tutorialMetaData.template ??= 'default'; tutorialMetaData.i18n = Object.assign({ ...DEFAULT_LOCALIZATION }, tutorialMetaData.i18n); + tutorialMetaData.openInStackBlitzLink ??= true; _tutorial.logoLink = data.logoLink; } else if (type === 'part') { @@ -257,6 +258,7 @@ export async function getTutorial(): Promise { 'focus', 'i18n', 'editPageLink', + 'openInStackBlitzLink', ], ), }; diff --git a/packages/cli/src/commands/eject/index.ts b/packages/cli/src/commands/eject/index.ts index 6ea444f83..d7070718c 100644 --- a/packages/cli/src/commands/eject/index.ts +++ b/packages/cli/src/commands/eject/index.ts @@ -18,7 +18,14 @@ interface PackageJson { } const TUTORIALKIT_VERSION = pkg.version; -const REQUIRED_DEPENDENCIES = ['@tutorialkit/runtime', '@webcontainer/api', 'nanostores', '@nanostores/react', 'kleur']; +const REQUIRED_DEPENDENCIES = [ + '@tutorialkit/runtime', + '@webcontainer/api', + 'nanostores', + '@nanostores/react', + 'kleur', + '@stackblitz/sdk', +]; export function ejectRoutes(flags: Arguments) { if (flags._[1] === 'help' || flags.help || flags.h) { diff --git a/packages/cli/tests/__snapshots__/create-tutorial.test.ts.snap b/packages/cli/tests/__snapshots__/create-tutorial.test.ts.snap index 31799e804..ee67bba96 100644 --- a/packages/cli/tests/__snapshots__/create-tutorial.test.ts.snap +++ b/packages/cli/tests/__snapshots__/create-tutorial.test.ts.snap @@ -157,6 +157,7 @@ exports[`create and eject a project 1`] = ` "src/components/MobileContentToggle.astro", "src/components/NavCard.astro", "src/components/NavWrapper.tsx", + "src/components/OpenInStackblitzLink.astro", "src/components/PageLoadingIndicator.astro", "src/components/ResizablePanel.astro", "src/components/ThemeSwitch.tsx", diff --git a/packages/runtime/src/store/index.ts b/packages/runtime/src/store/index.ts index e2349a787..42f363da6 100644 --- a/packages/runtime/src/store/index.ts +++ b/packages/runtime/src/store/index.ts @@ -207,6 +207,10 @@ export class TutorialStore { return this._editorStore.documents; } + get template(): Files | undefined { + return this._lessonTemplate; + } + get selectedFile(): ReadableAtom { return this._editorStore.selectedFile; } diff --git a/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md b/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md index b29878de1..bac12737e 100644 --- a/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md +++ b/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md @@ -1,4 +1,5 @@ --- type: chapter title: The first chatper in part 2 +openInStackBlitzLink: false --- diff --git a/packages/types/src/schemas/common.ts b/packages/types/src/schemas/common.ts index e071489be..d64a8198e 100644 --- a/packages/types/src/schemas/common.ts +++ b/packages/types/src/schemas/common.ts @@ -212,6 +212,7 @@ export const webcontainerSchema = commandsSchema.extend({ .describe( 'Display a link in lesson for editing the page content. The value is a URL pattern where `${path}` is replaced with the lesson’s location relative to `src/content/tutorial`.', ), + openInStackBlitzLink: z.boolean().optional().describe('Display a link for opening current lesson in StackBlitz.'), }); export const baseSchema = webcontainerSchema.extend({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7d307151..bebf8c762 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -251,6 +251,9 @@ importers: '@nanostores/react': specifier: 0.7.2 version: 0.7.2(nanostores@0.10.3)(react@18.3.1) + '@stackblitz/sdk': + specifier: ^1.11.0 + version: 1.11.0 '@tutorialkit/components-react': specifier: workspace:* version: link:../components/react @@ -3079,6 +3082,10 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + /@stackblitz/sdk@1.11.0: + resolution: {integrity: sha512-DFQGANNkEZRzFk1/rDP6TcFdM82ycHE+zfl9C/M/jXlH68jiqHWHFMQURLELoD8koxvu/eW5uhg94NSAZlYrUQ==} + dev: false + /@stylistic/eslint-plugin-js@2.2.2(eslint@9.5.0): resolution: {integrity: sha512-Vj2Q1YHVvJw+ThtOvmk5Yx7wZanVrIBRUTT89horLDb4xdP9GA1um9XOYQC6j67VeUC2gjZQnz5/RVJMzaOhtw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} From f5d402e12bb7ae77b824cca95b987adfc4230cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Tue, 6 Aug 2024 15:49:28 +0300 Subject: [PATCH 2/7] feat: Icon-only button --- .../src/default/components/OpenInStackblitzLink.astro | 8 +++++--- packages/astro/src/default/components/TopBar.astro | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/astro/src/default/components/OpenInStackblitzLink.astro b/packages/astro/src/default/components/OpenInStackblitzLink.astro index e740b6746..413cd2311 100644 --- a/packages/astro/src/default/components/OpenInStackblitzLink.astro +++ b/packages/astro/src/default/components/OpenInStackblitzLink.astro @@ -1,10 +1,12 @@ diff --git a/packages/runtime/src/store/index.ts b/packages/runtime/src/store/index.ts index 42f363da6..cdb6f3572 100644 --- a/packages/runtime/src/store/index.ts +++ b/packages/runtime/src/store/index.ts @@ -356,4 +356,8 @@ export class TutorialStore { refreshStyles() { this._themeRef.set(this._themeRef.get() + 1); } + + takeSnapshot() { + return this._runner.takeSnapshot(); + } } diff --git a/packages/runtime/src/tutorial-runner.ts b/packages/runtime/src/tutorial-runner.ts index cb7d7c442..aae1cbe0a 100644 --- a/packages/runtime/src/tutorial-runner.ts +++ b/packages/runtime/src/tutorial-runner.ts @@ -302,6 +302,43 @@ export class TutorialRunner { ); } + /** + * Get snapshot of runner's current files. + * Also prepares `package.json`'s `stackblitz.startCommand` with runner's commands. + * + * Note that file paths do not contain the leading `/`. + */ + takeSnapshot() { + const files: Record = {}; + + // first add template files + for (const [filePath, value] of Object.entries(this._currentTemplate || {})) { + if (typeof value === 'string') { + files[removeLeadingSlash(filePath)] = value; + } + } + + // next overwrite with files from editor + for (const [filePath, value] of Object.entries(this._currentFiles || {})) { + if (typeof value === 'string') { + files[removeLeadingSlash(filePath)] = value; + } + } + + const packageJson = parseJson(files['package.json']); + + // add start commands + if (files['package.json']) { + const mainCommand = this._currentRunCommands?.mainCommand?.shellCommand; + const prepareCommands = (this._currentRunCommands?.prepareCommands || []).map((c) => c.shellCommand); + const startCommand = [...prepareCommands, mainCommand].filter(Boolean).join(' && '); + + files['package.json'] = JSON.stringify({ ...packageJson, stackblitz: { startCommand } }, null, 2); + } + + return { files }; + } + private async _runCommands(webcontainer: WebContainer, commands: Commands, signal: AbortSignal) { const output = this._terminalStore.getOutputPanel(); @@ -484,3 +521,19 @@ async function updateFiles(webcontainer: WebContainer, previousFiles: Files, new await webcontainer.mount(toFileTree(addedOrModified)); } + +function removeLeadingSlash(filePath: string) { + if (filePath.startsWith('/')) { + return filePath.slice(1); + } + + return filePath; +} + +function parseJson(deserialized: undefined | string): any { + try { + return JSON.parse(deserialized || '{}'); + } catch { + return {}; + } +} From b0104254cc851f8ced7ed054f380c9a3e92f13e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Wed, 7 Aug 2024 08:43:09 +0300 Subject: [PATCH 5/7] fix: code review --- packages/runtime/src/tutorial-runner.ts | 40 ++++++++++--------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/runtime/src/tutorial-runner.ts b/packages/runtime/src/tutorial-runner.ts index aae1cbe0a..45381de54 100644 --- a/packages/runtime/src/tutorial-runner.ts +++ b/packages/runtime/src/tutorial-runner.ts @@ -314,26 +314,32 @@ export class TutorialRunner { // first add template files for (const [filePath, value] of Object.entries(this._currentTemplate || {})) { if (typeof value === 'string') { - files[removeLeadingSlash(filePath)] = value; + files[filePath.slice(1)] = value; } } // next overwrite with files from editor for (const [filePath, value] of Object.entries(this._currentFiles || {})) { if (typeof value === 'string') { - files[removeLeadingSlash(filePath)] = value; + files[filePath.slice(1)] = value; } } - const packageJson = parseJson(files['package.json']); - - // add start commands if (files['package.json']) { - const mainCommand = this._currentRunCommands?.mainCommand?.shellCommand; - const prepareCommands = (this._currentRunCommands?.prepareCommands || []).map((c) => c.shellCommand); - const startCommand = [...prepareCommands, mainCommand].filter(Boolean).join(' && '); + let packageJson; + + try { + packageJson = JSON.parse(files['package.json']); + } catch {} + + // add start commands when missing + if (packageJson && !packageJson.stackblitz?.startCommand) { + const mainCommand = this._currentRunCommands?.mainCommand?.shellCommand; + const prepareCommands = (this._currentRunCommands?.prepareCommands || []).map((c) => c.shellCommand); + const startCommand = [...prepareCommands, mainCommand].filter(Boolean).join(' && '); - files['package.json'] = JSON.stringify({ ...packageJson, stackblitz: { startCommand } }, null, 2); + files['package.json'] = JSON.stringify({ ...packageJson, stackblitz: { startCommand } }, null, 2); + } } return { files }; @@ -521,19 +527,3 @@ async function updateFiles(webcontainer: WebContainer, previousFiles: Files, new await webcontainer.mount(toFileTree(addedOrModified)); } - -function removeLeadingSlash(filePath: string) { - if (filePath.startsWith('/')) { - return filePath.slice(1); - } - - return filePath; -} - -function parseJson(deserialized: undefined | string): any { - try { - return JSON.parse(deserialized || '{}'); - } catch { - return {}; - } -} From 4d80275a7388380cc2f949c4c8a0a088a8a93773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Wed, 7 Aug 2024 10:57:43 +0300 Subject: [PATCH 6/7] fix: code review --- .../src/content/docs/reference/configuration.mdx | 13 +++++++++++-- .../default/components/OpenInStackblitzLink.astro | 10 ++++++---- .../src/default/components/TopBarWrapper.astro | 7 ++++--- packages/astro/src/default/pages/[...slug].astro | 2 +- packages/astro/src/default/utils/content.ts | 4 ++-- packages/runtime/src/tutorial-runner.ts | 14 ++++++++++---- .../src/content/tutorial/1-basics/2-foo/meta.md | 1 + .../content/tutorial/2-advanced/1-unicorn/meta.md | 2 +- packages/template/src/content/tutorial/meta.md | 3 +++ packages/types/src/schemas/common.ts | 13 ++++++++++++- 10 files changed, 51 insertions(+), 18 deletions(-) diff --git a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx index 53474606b..cc30eefe9 100644 --- a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx +++ b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx @@ -211,6 +211,15 @@ You can instruct Github to show the source code instead by adding `plain=1` quer ::: -### `openInStackBlitzLink` +### `openInStackBlitz` Display a link for opening current lesson in StackBlitz. - + + +The `OpenInStackBlitz` type has the following shape: + +```ts +type OpenInStackBlitz = string + | boolean + | { projectTitle?: string, projectDescription?: string } + +``` \ No newline at end of file diff --git a/packages/astro/src/default/components/OpenInStackblitzLink.astro b/packages/astro/src/default/components/OpenInStackblitzLink.astro index 7b6710591..2e8d3ef8a 100644 --- a/packages/astro/src/default/components/OpenInStackblitzLink.astro +++ b/packages/astro/src/default/components/OpenInStackblitzLink.astro @@ -13,7 +13,7 @@ import StackBlitzSDK from '@stackblitz/sdk'; import { tutorialStore } from '../components/webcontainer.js'; - // initialize handlers on each page load as it's possible some pages disable openInStackBlitzLink + // initialize handlers on each page load as it's possible some pages disable openInStackBlitz document.addEventListener('astro:page-load', onInit); function onInit() { @@ -23,15 +23,17 @@ function onClick() { const lesson = tutorialStore.lesson; - const snapshot = tutorialStore.takeSnapshot(); if (!lesson) { throw new Error('Missing lesson'); } + const snapshot = tutorialStore.takeSnapshot(); + const options = typeof lesson.data.openInStackBlitz === 'object' ? lesson.data.openInStackBlitz : {}; + StackBlitzSDK.openProject({ - title: lesson.data.title || 'Lesson', - description: `${lesson.part.title} / ${lesson.chapter.title}`, + title: options.projectTitle || 'Project generated by TutorialKit', + description: options.projectDescription, template: 'node', files: snapshot.files, }); diff --git a/packages/astro/src/default/components/TopBarWrapper.astro b/packages/astro/src/default/components/TopBarWrapper.astro index 3bc5a625c..6ea8b0cc7 100644 --- a/packages/astro/src/default/components/TopBarWrapper.astro +++ b/packages/astro/src/default/components/TopBarWrapper.astro @@ -1,5 +1,6 @@ --- import { TopBar } from 'tutorialkit:override-components'; +import type { Lesson } from '@tutorialkit/types'; import { ThemeSwitch } from './ThemeSwitch'; import { LoginButton } from './LoginButton'; import OpenInStackblitzLink from './OpenInStackblitzLink.astro'; @@ -8,16 +9,16 @@ import { useAuth } from './setup'; interface Props { logoLink: string; - openInStackBlitzLink: boolean | undefined; + openInStackBlitz: Lesson['data']['openInStackBlitz']; } -const { logoLink, openInStackBlitzLink: showOpenInStackBlitzLink } = Astro.props; +const { logoLink, openInStackBlitz } = Astro.props; --- - {showOpenInStackBlitzLink && } + {openInStackBlitz && } diff --git a/packages/astro/src/default/pages/[...slug].astro b/packages/astro/src/default/pages/[...slug].astro index cd3fd20db..5738adb89 100644 --- a/packages/astro/src/default/pages/[...slug].astro +++ b/packages/astro/src/default/pages/[...slug].astro @@ -21,7 +21,7 @@ const { lesson, logoLink, navList, title } = Astro.props as Props;
- +
diff --git a/packages/astro/src/default/utils/content.ts b/packages/astro/src/default/utils/content.ts index d0e0e7b41..f70743fb2 100644 --- a/packages/astro/src/default/utils/content.ts +++ b/packages/astro/src/default/utils/content.ts @@ -41,7 +41,7 @@ export async function getTutorial(): Promise { // default template if not specified tutorialMetaData.template ??= 'default'; tutorialMetaData.i18n = Object.assign({ ...DEFAULT_LOCALIZATION }, tutorialMetaData.i18n); - tutorialMetaData.openInStackBlitzLink ??= true; + tutorialMetaData.openInStackBlitz ??= true; _tutorial.logoLink = data.logoLink; } else if (type === 'part') { @@ -258,7 +258,7 @@ export async function getTutorial(): Promise { 'focus', 'i18n', 'editPageLink', - 'openInStackBlitzLink', + 'openInStackBlitz', ], ), }; diff --git a/packages/runtime/src/tutorial-runner.ts b/packages/runtime/src/tutorial-runner.ts index 45381de54..1d73bbc69 100644 --- a/packages/runtime/src/tutorial-runner.ts +++ b/packages/runtime/src/tutorial-runner.ts @@ -65,6 +65,7 @@ export class TutorialRunner { // this strongly assumes that there's a single package json which might not be true private _packageJsonContent = ''; + private _packageJsonPath = ''; constructor( private _webcontainer: Promise, @@ -188,7 +189,7 @@ export class TutorialRunner { this._currentTemplate = { ...template }; this._currentFiles = { ...files }; - this._updateDirtyState(files); + this._updateDirtyState({ ...template, ...files }); }, { ignoreCancel: true, signal }, ); @@ -325,11 +326,11 @@ export class TutorialRunner { } } - if (files['package.json']) { + if (this._packageJsonContent) { let packageJson; try { - packageJson = JSON.parse(files['package.json']); + packageJson = JSON.parse(this._packageJsonContent); } catch {} // add start commands when missing @@ -338,7 +339,11 @@ export class TutorialRunner { const prepareCommands = (this._currentRunCommands?.prepareCommands || []).map((c) => c.shellCommand); const startCommand = [...prepareCommands, mainCommand].filter(Boolean).join(' && '); - files['package.json'] = JSON.stringify({ ...packageJson, stackblitz: { startCommand } }, null, 2); + files[this._packageJsonPath.slice(1)] = JSON.stringify( + { ...packageJson, stackblitz: { startCommand } }, + null, + 2, + ); } } @@ -460,6 +465,7 @@ export class TutorialRunner { for (const filePath in files) { if (filePath.endsWith('/package.json') && files[filePath] != this._packageJsonContent) { this._packageJsonContent = files[filePath] as string; + this._packageJsonPath = filePath; this._packageJsonDirty = true; return; diff --git a/packages/template/src/content/tutorial/1-basics/2-foo/meta.md b/packages/template/src/content/tutorial/1-basics/2-foo/meta.md index 580654852..df136d4ab 100644 --- a/packages/template/src/content/tutorial/1-basics/2-foo/meta.md +++ b/packages/template/src/content/tutorial/1-basics/2-foo/meta.md @@ -1,4 +1,5 @@ --- type: chapter title: The second chapter in part 1 +openInStackBlitz: true --- diff --git a/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md b/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md index bac12737e..84d84f723 100644 --- a/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md +++ b/packages/template/src/content/tutorial/2-advanced/1-unicorn/meta.md @@ -1,5 +1,5 @@ --- type: chapter title: The first chatper in part 2 -openInStackBlitzLink: false +openInStackBlitz: false --- diff --git a/packages/template/src/content/tutorial/meta.md b/packages/template/src/content/tutorial/meta.md index 0e259348f..96727ddf1 100644 --- a/packages/template/src/content/tutorial/meta.md +++ b/packages/template/src/content/tutorial/meta.md @@ -9,4 +9,7 @@ prepareCommands: - ['npm install', 'Installing dependencies'] i18n: partTemplate: ${title} +openInStackBlitz: + projectTitle: Example Title + projectDescription: Example Description --- diff --git a/packages/types/src/schemas/common.ts b/packages/types/src/schemas/common.ts index d64a8198e..59358e5c4 100644 --- a/packages/types/src/schemas/common.ts +++ b/packages/types/src/schemas/common.ts @@ -212,7 +212,18 @@ export const webcontainerSchema = commandsSchema.extend({ .describe( 'Display a link in lesson for editing the page content. The value is a URL pattern where `${path}` is replaced with the lesson’s location relative to `src/content/tutorial`.', ), - openInStackBlitzLink: z.boolean().optional().describe('Display a link for opening current lesson in StackBlitz.'), + openInStackBlitz: z + .union([ + // `false` for disabling the link + z.boolean(), + + z.strictObject({ + projectTitle: z.string().optional(), + projectDescription: z.string().optional(), + }), + ]) + .optional() + .describe('Display a link for opening current lesson in StackBlitz.'), }); export const baseSchema = webcontainerSchema.extend({ From 946807e4a2ef04721a20b8b88e74e84991bac735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Wed, 7 Aug 2024 11:15:11 +0300 Subject: [PATCH 7/7] fix: code review --- .../src/content/docs/reference/configuration.mdx | 6 ++++-- .../default/components/OpenInStackblitzLink.astro | 2 +- packages/types/src/schemas/common.ts | 12 ++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx index cc30eefe9..0c64a0deb 100644 --- a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx +++ b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx @@ -218,8 +218,10 @@ Display a link for opening current lesson in StackBlitz. The `OpenInStackBlitz` type has the following shape: ```ts -type OpenInStackBlitz = string +type OpenInStackBlitz = | boolean - | { projectTitle?: string, projectDescription?: string } + | { projectTitle?: string, projectDescription?: string, projectTemplate?: TemplateType } + +type TemplateType = "html" | "node" | "angular-cli" | "create-react-app" | "javascript" | "polymer" | "typescript" | "vue" ``` \ No newline at end of file diff --git a/packages/astro/src/default/components/OpenInStackblitzLink.astro b/packages/astro/src/default/components/OpenInStackblitzLink.astro index 2e8d3ef8a..1dd06becd 100644 --- a/packages/astro/src/default/components/OpenInStackblitzLink.astro +++ b/packages/astro/src/default/components/OpenInStackblitzLink.astro @@ -34,7 +34,7 @@ StackBlitzSDK.openProject({ title: options.projectTitle || 'Project generated by TutorialKit', description: options.projectDescription, - template: 'node', + template: options.projectTemplate || 'node', files: snapshot.files, }); } diff --git a/packages/types/src/schemas/common.ts b/packages/types/src/schemas/common.ts index 59358e5c4..06d36ec54 100644 --- a/packages/types/src/schemas/common.ts +++ b/packages/types/src/schemas/common.ts @@ -220,6 +220,18 @@ export const webcontainerSchema = commandsSchema.extend({ z.strictObject({ projectTitle: z.string().optional(), projectDescription: z.string().optional(), + projectTemplate: z + .union([ + z.literal('html'), + z.literal('node'), + z.literal('angular-cli'), + z.literal('create-react-app'), + z.literal('javascript'), + z.literal('polymer'), + z.literal('typescript'), + z.literal('vue'), + ]) + .optional(), }), ]) .optional()