diff --git a/web/docs/tutorial/01-create.md b/web/docs/tutorial/01-create.md index c4a9bd213e..2a37a27573 100644 --- a/web/docs/tutorial/01-create.md +++ b/web/docs/tutorial/01-create.md @@ -3,6 +3,7 @@ title: 1. Creating a New Project --- import useBaseUrl from '@docusaurus/useBaseUrl'; +import { TutorialAction } from './TutorialAction'; :::info You'll need to have the latest version of Wasp installed locally to follow this tutorial. If you haven't installed it yet, check out the [QuickStart](../quick-start) guide! @@ -24,9 +25,12 @@ You can find the complete code of the app we're about to build [here](https://gi To setup a new Wasp project, run the following command in your terminal: + + ```sh wasp new TodoApp -t minimal ``` + diff --git a/web/docs/tutorial/03-pages.md b/web/docs/tutorial/03-pages.md index cc849778f6..b13980cae7 100644 --- a/web/docs/tutorial/03-pages.md +++ b/web/docs/tutorial/03-pages.md @@ -6,6 +6,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import { ShowForTs } from '@site/src/components/TsJsHelpers'; import WaspStartNote from '../\_WaspStartNote.md' import TypescriptServerNote from '../\_TypescriptServerNote.md' +import { TutorialAction } from './TutorialAction'; In the default `main.wasp` file created by `wasp new`, there is a **page** and a **route** declaration: @@ -47,7 +48,7 @@ import './Main.css'; export function MainPage() { // ... -} +}; ``` This is a regular functional React component. It also imports some CSS and a logo from the `assets` folder. @@ -74,12 +75,12 @@ page HelloPage { When a user visits `/hello/their-name`, Wasp renders the component exported from `src/HelloPage.{jsx,tsx}` and you can use the `useParams` hook from `react-router-dom` to access the `name` parameter: ```tsx title="src/HelloPage.tsx" auto-js -import { useParams } from 'react-router-dom' +import { useParams } from "react-router-dom"; export const HelloPage = () => { - const { name } = useParams<'name'>() - return
Here's {name}!
-} + const { name } = useParams<"name">(); + return
Here's {name}!
; +}; ``` Now you can visit `/hello/johnny` and see "Here's johnny!" @@ -96,12 +97,14 @@ Now that you've seen how Wasp deals with Routes and Pages, it's finally time to Start by cleaning up the starter project and removing unnecessary code and files. + + First, remove most of the code from the `MainPage` component: ```tsx title="src/MainPage.tsx" auto-js export const MainPage = () => { - return
Hello world!
-} + return
Hello world!
; +}; ``` At this point, the main page should look like this: @@ -130,6 +133,7 @@ page MainPage { component: import { MainPage } from "@src/MainPage" } ``` +
Excellent work! diff --git a/web/docs/tutorial/04-entities.md b/web/docs/tutorial/04-entities.md index 04bd4b8878..ab75b28c36 100644 --- a/web/docs/tutorial/04-entities.md +++ b/web/docs/tutorial/04-entities.md @@ -3,6 +3,7 @@ title: 4. Database Entities --- import useBaseUrl from '@docusaurus/useBaseUrl'; +import { TutorialAction } from './TutorialAction'; Entities are one of the most important concepts in Wasp and are how you define what gets stored in the database. @@ -10,6 +11,8 @@ Wasp uses Prisma to talk to the database, and you define Entities by defining Pr Since our Todo app is all about tasks, we'll define a Task entity by adding a Task model in the `schema.prisma` file: + + ```prisma title="schema.prisma" // ... @@ -19,6 +22,7 @@ model Task { isDone Boolean @default(false) } ``` + :::note Read more about how Wasp Entities work in the [Entities](../data-model/entities.md) section or how Wasp uses the `schema.prisma` file in the [Prisma Schema File](../data-model/prisma-file.md) section. @@ -26,6 +30,7 @@ Read more about how Wasp Entities work in the [Entities](../data-model/entities. To update the database schema to include this entity, stop the `wasp start` process, if it's running, and run: + ```sh wasp db migrate-dev ``` diff --git a/web/docs/tutorial/05-queries.md b/web/docs/tutorial/05-queries.md index 65270c78e0..8c111df5c3 100644 --- a/web/docs/tutorial/05-queries.md +++ b/web/docs/tutorial/05-queries.md @@ -4,6 +4,7 @@ title: 5. Querying the Database import useBaseUrl from '@docusaurus/useBaseUrl'; import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'; +import { TutorialAction } from './TutorialAction'; We want to know which tasks we need to do, so let's list them! @@ -24,6 +25,7 @@ We'll create a new Query called `getTasks`. We'll need to declare the Query in t We need to add a **query** declaration to `main.wasp` so that Wasp knows it exists: + ```wasp title="main.wasp" @@ -39,6 +41,7 @@ We need to add a **query** declaration to `main.wasp` so that Wasp knows it exis entities: [Task] } ``` + @@ -59,8 +62,10 @@ We need to add a **query** declaration to `main.wasp` so that Wasp knows it exis :::note To generate the types used in the next section, make sure that `wasp start` is still running. ::: + + ### Implementing a Query @@ -72,17 +77,19 @@ We need to add a **query** declaration to `main.wasp` so that Wasp knows it exis Next, create a new file called `src/queries.ts` and define the TypeScript function we've just imported in our `query` declaration: + + ```ts title="src/queries.ts" auto-js -import type { Task } from 'wasp/entities' -import type { GetTasks } from 'wasp/server/operations' +import type { Task } from "wasp/entities"; +import type { GetTasks } from "wasp/server/operations"; export const getTasks: GetTasks = async (args, context) => { return context.entities.Task.findMany({ - orderBy: { id: 'asc' }, - }) -} + orderBy: { id: "asc" }, + }); +}; ``` - + Wasp automatically generates the types `GetTasks` and `Task` based on the contents of `main.wasp`: @@ -116,25 +123,27 @@ While we implement Queries on the server, Wasp generates client-side functions t This makes it easy for us to use the `getTasks` Query we just created in our React component: + + ```tsx title="src/MainPage.tsx" auto-js -import type { Task } from 'wasp/entities' +import type { Task } from "wasp/entities"; // highlight-next-line -import { getTasks, useQuery } from 'wasp/client/operations' +import { getTasks, useQuery } from "wasp/client/operations"; export const MainPage = () => { // highlight-start - const { data: tasks, isLoading, error } = useQuery(getTasks) + const { data: tasks, isLoading, error } = useQuery(getTasks); return (
{tasks && } - {isLoading && 'Loading...'} - {error && 'Error: ' + error} + {isLoading && "Loading..."} + {error && "Error: " + error}
- ) + ); // highlight-end -} +}; // highlight-start const TaskView = ({ task }: { task: Task }) => { @@ -143,11 +152,11 @@ const TaskView = ({ task }: { task: Task }) => { {task.description} - ) -} + ); +}; const TasksList = ({ tasks }: { tasks: Task[] }) => { - if (!tasks?.length) return
No tasks
+ if (!tasks?.length) return
No tasks
; return (
@@ -155,10 +164,11 @@ const TasksList = ({ tasks }: { tasks: Task[] }) => { ))}
- ) -} + ); +}; // highlight-end ``` +
Most of this code is regular React, the only exception being the twothree special `wasp` imports: @@ -172,7 +182,7 @@ Most of this code is regular React, the only exception being the two< - `useQuery` - Wasp's [useQuery](../data-model/operations/queries#the-usequery-hook-1) React hook, which is based on [react-query](https://github.com/tannerlinsley/react-query)'s hook with the same name. - `Task` - The type for the Task entity defined in `schema.prisma`. - Notice how you don't need to annotate the type of the Query's return value: Wasp uses the types you defined while implementing the Query for the generated client-side function. This is **full-stack type safety**: the types on the client always match the types on the server. +Notice how you don't need to annotate the type of the Query's return value: Wasp uses the types you defined while implementing the Query for the generated client-side function. This is **full-stack type safety**: the types on the client always match the types on the server.
We could have called the Query directly using `getTasks()`, but the `useQuery` hook makes it reactive: React will re-render the component every time the Query changes. Remember that Wasp automatically refreshes Queries whenever the data is modified. diff --git a/web/docs/tutorial/06-actions.md b/web/docs/tutorial/06-actions.md index 4ae5d6840f..a00d9b9935 100644 --- a/web/docs/tutorial/06-actions.md +++ b/web/docs/tutorial/06-actions.md @@ -5,6 +5,7 @@ title: 6. Modifying Data import useBaseUrl from '@docusaurus/useBaseUrl'; import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'; import Collapse from '@site/src/components/Collapse'; +import { TutorialAction } from './TutorialAction'; In the previous section, you learned about using Queries to fetch data. Let's now learn about Actions so you can add and update tasks in the database. @@ -22,6 +23,8 @@ Creating an Action is very similar to creating a Query. We must first declare the Action in `main.wasp`: + + ```wasp title="main.wasp" // ... @@ -30,32 +33,35 @@ action createTask { entities: [Task] } ``` + ### Implementing an Action Let's now define a JavaScriptTypeScript function for our `createTask` Action: + + ```ts title="src/actions.ts" auto-js -import type { Task } from 'wasp/entities' -import type { CreateTask } from 'wasp/server/operations' +import type { Task } from "wasp/entities"; +import type { CreateTask } from "wasp/server/operations"; -type CreateTaskPayload = Pick +type CreateTaskPayload = Pick; export const createTask: CreateTask = async ( args, - context + context, ) => { return context.entities.Task.create({ data: { description: args.description }, - }) -} + }); +}; ``` + Once again, we've annotated the Action with the `CreateTask` and `Task` types generated by Wasp. Just like with queries, defining the types on the implementation makes them available on the frontend, giving us **full-stack type safety**. - :::tip We put the function in a new file `src/actions.{js,ts}`, but we could have put it anywhere we wanted! There are no limitations here, as long as the declaration in the Wasp file imports it correctly and the file is located within `src` directory. ::: @@ -64,41 +70,44 @@ We put the function in a new file `src/actions.{js,ts}`, but we could have put i Start by defining a form for creating new tasks. + + ```tsx title="src/MainPage.tsx" auto-js -import type { FormEvent } from 'react' -import type { Task } from 'wasp/entities' +import type { FormEvent } from "react"; +import type { Task } from "wasp/entities"; import { // highlight-next-line createTask, getTasks, - useQuery -} from 'wasp/client/operations' + useQuery, +} from "wasp/client/operations"; // ... MainPage, TaskView, TaskList ... // highlight-start const NewTaskForm = () => { const handleSubmit = async (event: FormEvent) => { - event.preventDefault() + event.preventDefault(); try { - const target = event.target as HTMLFormElement - const description = target.description.value - target.reset() - await createTask({ description }) + const target = event.target as HTMLFormElement; + const description = target.description.value; + target.reset(); + await createTask({ description }); } catch (err: any) { - window.alert('Error: ' + err.message) + window.alert("Error: " + err.message); } - } + }; return (
- ) -} + ); +}; // highlight-end ``` +
Unlike Queries, you can call Actions directly (without wrapping them in a hook) because they don't need reactivity. The rest is just regular React code. @@ -108,27 +117,30 @@ Unlike Queries, you can call Actions directly (without wrapping them in a hook) All that's left now is adding this form to the page component: + + ```tsx title="src/MainPage.tsx" auto-js -import type { FormEvent } from 'react' -import type { Task } from 'wasp/entities' -import { createTask, getTasks, useQuery } from 'wasp/client/operations' +import type { FormEvent } from "react"; +import type { Task } from "wasp/entities"; +import { createTask, getTasks, useQuery } from "wasp/client/operations"; const MainPage = () => { - const { data: tasks, isLoading, error } = useQuery(getTasks) + const { data: tasks, isLoading, error } = useQuery(getTasks); return (
// highlight-next-line {tasks && } - {isLoading && 'Loading...'} - {error && 'Error: ' + error} + {isLoading && "Loading..."} + {error && "Error: " + error}
- ) -} + ); +}; // ... TaskList, TaskView, NewTaskForm ... ``` +
Great work! @@ -163,51 +175,60 @@ Since we've already created one task together, try to create this one yourself. Declaring the Action in `main.wasp`: - ```wasp title="main.wasp" - // ... + - action updateTask { - fn: import { updateTask } from "@src/actions", - entities: [Task] - } - ``` +```wasp title="main.wasp" +// ... - Implementing the Action on the server: +action updateTask { + fn: import { updateTask } from "@src/actions", + entities: [Task] +} +``` + + +Implementing the Action on the server: + + + +```ts title="src/actions.ts" auto-js +import type { CreateTask, UpdateTask } from "wasp/server/operations"; + +// ... - ```ts title="src/actions.ts" auto-js - import type { CreateTask, UpdateTask } from 'wasp/server/operations' +type UpdateTaskPayload = Pick; - // ... +export const updateTask: UpdateTask = async ( + { id, isDone }, + context, +) => { + return context.entities.Task.update({ + where: { id }, + data: { + isDone: isDone, + }, + }); +}; +``` - type UpdateTaskPayload = Pick + - export const updateTask: UpdateTask = async ( - { id, isDone }, - context - ) => { - return context.entities.Task.update({ - where: { id }, - data: { - isDone: isDone, - }, - }) - } - ``` You can now call `updateTask` from the React component: + + ```tsx title="src/MainPage.tsx" auto-js -import type { FormEvent, ChangeEvent } from 'react' -import type { Task } from 'wasp/entities' +import type { FormEvent, ChangeEvent } from "react"; +import type { Task } from "wasp/entities"; import { // highlight-next-line updateTask, createTask, getTasks, useQuery, -} from 'wasp/client/operations' - +} from "wasp/client/operations"; // ... MainPage ... @@ -218,11 +239,11 @@ const TaskView = ({ task }: { task: Task }) => { await updateTask({ id: task.id, isDone: event.target.checked, - }) + }); } catch (error: any) { - window.alert('Error while updating task: ' + error.message) + window.alert("Error while updating task: " + error.message); } - } + }; // highlight-end return ( @@ -236,11 +257,12 @@ const TaskView = ({ task }: { task: Task }) => { /> {task.description} - ) -} + ); +}; // ... TaskList, NewTaskForm ... ``` + Awesome! You can now mark this task as done. diff --git a/web/docs/tutorial/07-auth.md b/web/docs/tutorial/07-auth.md index 5928e6eddc..f70cbb49ab 100644 --- a/web/docs/tutorial/07-auth.md +++ b/web/docs/tutorial/07-auth.md @@ -4,6 +4,7 @@ title: 7. Adding Authentication import useBaseUrl from '@docusaurus/useBaseUrl'; import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'; +import { TutorialAction } from './TutorialAction'; Most modern apps need a way to create and authenticate users. Wasp makes this as easy as possible with its first-class auth support. @@ -23,6 +24,8 @@ Since Wasp manages authentication, it will create [the auth related entities](.. You must only add the `User` Entity to keep track of who owns which tasks: + + ```prisma title="schema.prisma" // ... @@ -30,11 +33,14 @@ model User { id Int @id @default(autoincrement()) } ``` + ## Adding Auth to the Project Next, tell Wasp to use full-stack [authentication](../auth/overview): + + ```wasp title="main.wasp" app TodoApp { wasp: { @@ -60,9 +66,12 @@ app TodoApp { // ... ``` + Don't forget to update the database schema by running: + + ```sh wasp db migrate-dev ``` @@ -82,6 +91,8 @@ Wasp also supports authentication using [Google](../auth/social-auth/google), [G Wasp creates the login and signup forms for us, but we still need to define the pages to display those forms on. We'll start by declaring the pages in the Wasp file: + + ```wasp title="main.wasp" // ... @@ -95,46 +106,53 @@ page LoginPage { component: import { LoginPage } from "@src/LoginPage" } ``` + Great, Wasp now knows these pages exist! Here's the React code for the pages you've just imported: + + ```tsx title="src/LoginPage.tsx" auto-js -import { Link } from 'react-router-dom' -import { LoginForm } from 'wasp/client/auth' +import { Link } from "react-router-dom"; +import { LoginForm } from "wasp/client/auth"; export const LoginPage = () => { return ( -
+

I don't have an account yet (go to signup).
- ) -} + ); +}; ``` + The signup page is very similar to the login page: + + ```tsx title="src/SignupPage.tsx" auto-js -import { Link } from 'react-router-dom' -import { SignupForm } from 'wasp/client/auth' +import { Link } from "react-router-dom"; +import { SignupForm } from "wasp/client/auth"; export const SignupPage = () => { return ( -
+

I already have an account (go to login).
- ) -} + ); +}; ``` + :::tip Type-safe links @@ -146,6 +164,8 @@ export const SignupPage = () => { We don't want users who are not logged in to access the main page, because they won't be able to create any tasks. So let's make the page private by requiring the user to be logged in: + + ```wasp title="main.wasp" // ... @@ -155,20 +175,24 @@ page MainPage { component: import { MainPage } from "@src/MainPage" } ``` + Now that auth is required for this page, unauthenticated users will be redirected to `/login`, as we specified with `app.auth.onAuthFailedRedirectTo`. Additionally, when `authRequired` is `true`, the page's React component will be provided a `user` object as prop. + + ```tsx title="src/MainPage.tsx" auto-js -import type { AuthUser } from 'wasp/auth' +import type { AuthUser } from "wasp/auth"; // highlight-next-line export const MainPage = ({ user }: { user: AuthUser }) => { // Do something with the user // ... -} +}; ``` + Ok, time to test this out. Navigate to the main page (`/`) of the app. You'll get redirected to `/login`, where you'll be asked to authenticate. @@ -194,6 +218,8 @@ However, you will notice that if you try logging in as different users and creat First, let's define a one-to-many relation between users and tasks (check the [Prisma docs on relations](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations)): + + ```prisma title="schema.prisma" // ... @@ -213,9 +239,11 @@ model Task { userId Int? } ``` + As always, you must migrate the database after changing the Entities: + ```sh wasp db migrate-dev ``` @@ -232,41 +260,46 @@ Instead, we would do a data migration to take care of those tasks, even if it me Next, let's update the queries and actions to forbid access to non-authenticated users and to operate only on the currently logged-in user's tasks: + + ```ts title="src/queries.ts" auto-js -import type { Task } from 'wasp/entities' +import type { Task } from "wasp/entities"; // highlight-next-line -import { HttpError } from 'wasp/server' -import type { GetTasks } from 'wasp/server/operations' +import { HttpError } from "wasp/server"; +import type { GetTasks } from "wasp/server/operations"; export const getTasks: GetTasks = async (args, context) => { // highlight-start if (!context.user) { - throw new HttpError(401) + throw new HttpError(401); } // highlight-end return context.entities.Task.findMany({ // highlight-next-line where: { user: { id: context.user.id } }, - orderBy: { id: 'asc' }, - }) -} + orderBy: { id: "asc" }, + }); +}; ``` + + + ```ts title="src/actions.ts" auto-js -import type { Task } from 'wasp/entities' +import type { Task } from "wasp/entities"; // highlight-next-line -import { HttpError } from 'wasp/server' -import type { CreateTask, UpdateTask } from 'wasp/server/operations' +import { HttpError } from "wasp/server"; +import type { CreateTask, UpdateTask } from "wasp/server/operations"; -type CreateTaskPayload = Pick +type CreateTaskPayload = Pick; export const createTask: CreateTask = async ( args, - context + context, ) => { // highlight-start if (!context.user) { - throw new HttpError(401) + throw new HttpError(401); } // highlight-end return context.entities.Task.create({ @@ -275,10 +308,10 @@ export const createTask: CreateTask = async ( // highlight-next-line user: { connect: { id: context.user.id } }, }, - }) -} + }); +}; -type UpdateTaskPayload = Pick +type UpdateTaskPayload = Pick; export const updateTask: UpdateTask< UpdateTaskPayload, @@ -286,15 +319,16 @@ export const updateTask: UpdateTask< > = async (args, context) => { // highlight-start if (!context.user) { - throw new HttpError(401) + throw new HttpError(401); } // highlight-end return context.entities.Task.updateMany({ where: { id: args.id, user: { id: context.user.id } }, data: { isDone: args.isDone }, - }) -} + }); +}; ``` + :::note Due to how Prisma works, we had to convert `update` to `updateMany` in `updateTask` action to be able to specify the user id in `where`. @@ -316,10 +350,12 @@ You will see that each user has their tasks, just as we specified in our code! Last, but not least, let's add the logout functionality: + + ```tsx title="src/MainPage.tsx" auto-js with-hole // ... // highlight-next-line -import { logout } from 'wasp/client/auth' +import { logout } from "wasp/client/auth"; //... const MainPage = () => { @@ -330,9 +366,10 @@ const MainPage = () => { // highlight-next-line
- ) -} + ); +}; ``` +
This is it, we have a working authentication system, and our Todo app is multi-user! diff --git a/web/docs/tutorial/TutorialAction.tsx b/web/docs/tutorial/TutorialAction.tsx new file mode 100644 index 0000000000..0b26631321 --- /dev/null +++ b/web/docs/tutorial/TutorialAction.tsx @@ -0,0 +1,120 @@ +import { useState } from "react"; +/* +`TutorialAction` component is related to the Tutorial Actions Executor (TACTE) which you can find in the `web/tutorial-actions-executor` folder. + +Its main purpose is to provide metadata on how to execute tutorial actions programmatically (using TACTE) +Additionally, it renders tutorial action names during development for easier debugging. + +`TutorialAction` component is used in the `web/docs/tutorial/*.md` files to annotate specific tutorial actions. +*/ + +// IMPORTANT: If you change actions here, make sure to also update the types in `web/tutorial-actions-executor/src/actions/actions.ts`. +type ActionProps = { + id: string; +} & ( + | { + action: "INIT_APP"; + starterTemplateName: string; + } + | { + action: "APPLY_PATCH"; + } + | { + action: "MIGRATE_DB"; + } +); + +export function TutorialAction(props: React.PropsWithChildren) { + const isDevelopment = process.env.NODE_ENV !== "production"; + + return isDevelopment ? ( + + ) : ( + props.children + ); +} + +function TutorialActionDebugInfo({ + id, + action, + children, +}: React.PropsWithChildren) { + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(id); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + + return ( +
+
+
tutorial action: {action}
+
+ id: {id} + +
+
+ {children &&
{children}
} +
+ ); +} + +const containerStyle: React.CSSProperties = { + marginBottom: "1.5rem", +}; + +const headerStyle: React.CSSProperties = { + display: "flex", + gap: "0.5rem", + marginBottom: "0.5rem", + alignItems: "center", +}; + +const idTextStyle: React.CSSProperties = { + marginRight: "0.25rem", +}; + +const copyButtonStyle: React.CSSProperties = { + fontSize: "0.65rem", + cursor: "pointer", + background: "rgba(255, 255, 255, 0.2)", + border: "1px solid rgba(255, 255, 255, 0.3)", + borderRadius: "0.25rem", + padding: "0.125rem 0.375rem", + color: "white", + transition: "all 0.2s ease", +}; + +const childrenContainerStyle: React.CSSProperties = { + border: "1px dashed #ef4444", + padding: "1rem", + borderRadius: "0.5rem", +}; + +const pillStyle: React.CSSProperties = { + borderRadius: "0.375rem", + paddingLeft: "0.625rem", + paddingRight: "0.625rem", + paddingTop: "0.375rem", + paddingBottom: "0.375rem", + fontSize: "0.75rem", + fontWeight: "600", + color: "white", + boxShadow: "0 1px 2px rgba(0, 0, 0, 0.1)", +}; + +const tutorialActionPillStyle: React.CSSProperties = { + ...pillStyle, + backgroundColor: "#6b7280", +}; + +const actionPillStyle: React.CSSProperties = { + ...pillStyle, + backgroundColor: "#ef4444", + display: "flex", + alignItems: "center", +}; diff --git a/web/docs/tutorial/patches/03-pages__prepare-project.patch b/web/docs/tutorial/patches/03-pages__prepare-project.patch new file mode 100644 index 0000000000..09902d6e10 --- /dev/null +++ b/web/docs/tutorial/patches/03-pages__prepare-project.patch @@ -0,0 +1,162 @@ +diff --git a/src/Main.css b/src/Main.css +deleted file mode 100644 +index 9e93c7a..0000000 +--- a/src/Main.css ++++ /dev/null +@@ -1,103 +0,0 @@ +-* { +- -webkit-font-smoothing: antialiased; +- -moz-osx-font-smoothing: grayscale; +- box-sizing: border-box; +- margin: 0; +- padding: 0; +-} +- +-body { +- font-family: +- -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", +- "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; +-} +- +-#root { +- min-height: 100vh; +- display: flex; +- flex-direction: column; +- justify-content: center; +- align-items: center; +-} +- +-.container { +- margin: 3rem 3rem 10rem 3rem; +- max-width: 726px; +- display: flex; +- flex-direction: column; +- justify-content: center; +- align-items: center; +- text-align: center; +-} +- +-.logo { +- max-height: 200px; +- margin-bottom: 1rem; +-} +- +-.title { +- font-size: 4rem; +- font-weight: 700; +- margin-bottom: 1rem; +-} +- +-.content { +- font-size: 1.2rem; +- font-weight: 400; +- line-height: 2; +- margin-bottom: 3rem; +-} +- +-.buttons { +- display: flex; +- flex-direction: row; +- gap: 1rem; +-} +- +-.button { +- font-size: 1.2rem; +- font-weight: 700; +- text-decoration: none; +- padding: 1.2rem 1.5rem; +- border-radius: 10px; +-} +- +-.button-filled { +- color: black; +- background-color: #ffcc00; +- border: 2px solid #ffcc00; +- +- transition: all 0.2s ease-in-out; +-} +- +-.button-filled:hover { +- filter: brightness(0.95); +-} +- +-.button-outlined { +- color: black; +- background-color: transparent; +- border: 2px solid #ffcc00; +- +- transition: all 0.2s ease-in-out; +-} +- +-.button-outlined:hover { +- filter: brightness(0.95); +-} +- +-code { +- border-radius: 5px; +- border: 1px solid #ffcc00; +- padding: 0.2rem; +- background: #ffcc0044; +- font-family: +- Menlo, +- Monaco, +- Lucida Console, +- Liberation Mono, +- DejaVu Sans Mono, +- Bitstream Vera Sans Mono, +- Courier New, +- monospace; +-} +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index bdb62b3..144163a 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -1,37 +1,3 @@ +-import Logo from "./assets/logo.svg"; +-import "./Main.css"; +- +-export function MainPage() { +- return ( +-
+- wasp +- +-

Welcome to Wasp!

+- +-

+- This is page MainPage located at route /. +-
+- Open src/MainPage.tsx to edit it. +-

+- +- +-
+- ); +-} ++export const MainPage = () => { ++ return
Hello world!
; ++}; +diff --git a/src/assets/logo.svg b/src/assets/logo.svg +deleted file mode 100644 +index faa99ae..0000000 +--- a/src/assets/logo.svg ++++ /dev/null +@@ -1 +0,0 @@ +- +\ No newline at end of file diff --git a/web/docs/tutorial/patches/04-entities__prisma-task.patch b/web/docs/tutorial/patches/04-entities__prisma-task.patch new file mode 100644 index 0000000000..6e0d47c74a --- /dev/null +++ b/web/docs/tutorial/patches/04-entities__prisma-task.patch @@ -0,0 +1,14 @@ +diff --git a/schema.prisma b/schema.prisma +index 190e2a8..65d7d30 100644 +--- a/schema.prisma ++++ b/schema.prisma +@@ -8,3 +8,9 @@ datasource db { + generator client { + provider = "prisma-client-js" + } ++ ++model Task { ++ id Int @id @default(autoincrement()) ++ description String ++ isDone Boolean @default(false) ++} diff --git a/web/docs/tutorial/patches/05-queries__main-page-tasks.patch b/web/docs/tutorial/patches/05-queries__main-page-tasks.patch new file mode 100644 index 0000000000..7bd4028566 --- /dev/null +++ b/web/docs/tutorial/patches/05-queries__main-page-tasks.patch @@ -0,0 +1,42 @@ +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index 144163a..addc7ba 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -1,3 +1,36 @@ ++import { getTasks, useQuery } from "wasp/client/operations"; ++import type { Task } from "wasp/entities"; ++ + export const MainPage = () => { +- return
Hello world!
; ++ const { data: tasks, isLoading, error } = useQuery(getTasks); ++ ++ return ( ++
++ {tasks && } ++ ++ {isLoading && "Loading..."} ++ {error && "Error: " + error} ++
++ ); ++}; ++ ++const TaskView = ({ task }: { task: Task }) => { ++ return ( ++
++ ++ {task.description} ++
++ ); ++}; ++ ++const TasksList = ({ tasks }: { tasks: Task[] }) => { ++ if (!tasks?.length) return
No tasks
; ++ ++ return ( ++
++ {tasks.map((task, idx) => ( ++ ++ ))} ++
++ ); + }; diff --git a/web/docs/tutorial/patches/05-queries__query-get-tasks-impl.patch b/web/docs/tutorial/patches/05-queries__query-get-tasks-impl.patch new file mode 100644 index 0000000000..6b0c04357c --- /dev/null +++ b/web/docs/tutorial/patches/05-queries__query-get-tasks-impl.patch @@ -0,0 +1,14 @@ +diff --git a/src/queries.ts b/src/queries.ts +new file mode 100644 +index 0000000..4e4ba1b +--- /dev/null ++++ b/src/queries.ts +@@ -0,0 +1,8 @@ ++import type { Task } from "wasp/entities"; ++import type { GetTasks } from "wasp/server/operations"; ++ ++export const getTasks: GetTasks = async (args, context) => { ++ return context.entities.Task.findMany({ ++ orderBy: { id: "asc" }, ++ }); ++}; diff --git a/web/docs/tutorial/patches/05-queries__query-get-tasks.patch b/web/docs/tutorial/patches/05-queries__query-get-tasks.patch new file mode 100644 index 0000000000..9248897f55 --- /dev/null +++ b/web/docs/tutorial/patches/05-queries__query-get-tasks.patch @@ -0,0 +1,18 @@ +diff --git a/main.wasp b/main.wasp +index a5f1899..c3df1d1 100644 +--- a/main.wasp ++++ b/main.wasp +@@ -9,3 +9,13 @@ route RootRoute { path: "/", to: MainPage } + page MainPage { + component: import { MainPage } from "@src/MainPage" + } ++ ++query getTasks { ++ // Specifies where the implementation for the query function is. ++ // The path `@src/queries` resolves to `src/queries.ts`. ++ // No need to specify an extension. ++ fn: import { getTasks } from "@src/queries", ++ // Tell Wasp that this query reads from the `Task` entity. Wasp will ++ // automatically update the results of this query when tasks are modified. ++ entities: [Task] ++} diff --git a/web/docs/tutorial/patches/06-actions__action-create-task-impl.patch b/web/docs/tutorial/patches/06-actions__action-create-task-impl.patch new file mode 100644 index 0000000000..f28f25bc5d --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__action-create-task-impl.patch @@ -0,0 +1,19 @@ +diff --git a/src/actions.ts b/src/actions.ts +new file mode 100644 +index 0000000..b8b2026 +--- /dev/null ++++ b/src/actions.ts +@@ -0,0 +1,13 @@ ++import type { Task } from "wasp/entities"; ++import type { CreateTask } from "wasp/server/operations"; ++ ++type CreateTaskPayload = Pick; ++ ++export const createTask: CreateTask = async ( ++ args, ++ context, ++) => { ++ return context.entities.Task.create({ ++ data: { description: args.description }, ++ }); ++}; diff --git a/web/docs/tutorial/patches/06-actions__action-create-task.patch b/web/docs/tutorial/patches/06-actions__action-create-task.patch new file mode 100644 index 0000000000..c0952faf52 --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__action-create-task.patch @@ -0,0 +1,13 @@ +diff --git a/main.wasp b/main.wasp +index c3df1d1..9b3c9d8 100644 +--- a/main.wasp ++++ b/main.wasp +@@ -19,3 +19,8 @@ query getTasks { + // automatically update the results of this query when tasks are modified. + entities: [Task] + } ++ ++action createTask { ++ fn: import { createTask } from "@src/actions", ++ entities: [Task] ++} diff --git a/web/docs/tutorial/patches/06-actions__action-update-task-impl.patch b/web/docs/tutorial/patches/06-actions__action-update-task-impl.patch new file mode 100644 index 0000000000..96085a3d01 --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__action-update-task-impl.patch @@ -0,0 +1,29 @@ +diff --git a/src/actions.ts b/src/actions.ts +index b8b2026..973d821 100644 +--- a/src/actions.ts ++++ b/src/actions.ts +@@ -1,5 +1,5 @@ + import type { Task } from "wasp/entities"; +-import type { CreateTask } from "wasp/server/operations"; ++import type { CreateTask, UpdateTask } from "wasp/server/operations"; + + type CreateTaskPayload = Pick; + +@@ -11,3 +11,17 @@ export const createTask: CreateTask = async ( + data: { description: args.description }, + }); + }; ++ ++type UpdateTaskPayload = Pick; ++ ++export const updateTask: UpdateTask = async ( ++ { id, isDone }, ++ context, ++) => { ++ return context.entities.Task.update({ ++ where: { id }, ++ data: { ++ isDone: isDone, ++ }, ++ }); ++}; diff --git a/web/docs/tutorial/patches/06-actions__action-update-task.patch b/web/docs/tutorial/patches/06-actions__action-update-task.patch new file mode 100644 index 0000000000..47c9e59df5 --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__action-update-task.patch @@ -0,0 +1,13 @@ +diff --git a/main.wasp b/main.wasp +index 9b3c9d8..eed193e 100644 +--- a/main.wasp ++++ b/main.wasp +@@ -24,3 +24,8 @@ action createTask { + fn: import { createTask } from "@src/actions", + entities: [Task] + } ++ ++action updateTask { ++ fn: import { updateTask } from "@src/actions", ++ entities: [Task] ++} diff --git a/web/docs/tutorial/patches/06-actions__main-page-create-task-impl-form.patch b/web/docs/tutorial/patches/06-actions__main-page-create-task-impl-form.patch new file mode 100644 index 0000000000..af1e804b0d --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__main-page-create-task-impl-form.patch @@ -0,0 +1,36 @@ +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index addc7ba..3f1732f 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -1,4 +1,5 @@ +-import { getTasks, useQuery } from "wasp/client/operations"; ++import type { FormEvent } from "react"; ++import { createTask, getTasks, useQuery } from "wasp/client/operations"; + import type { Task } from "wasp/entities"; + + export const MainPage = () => { +@@ -34,3 +35,24 @@ const TasksList = ({ tasks }: { tasks: Task[] }) => { +
+ ); + }; ++ ++const NewTaskForm = () => { ++ const handleSubmit = async (event: FormEvent) => { ++ event.preventDefault(); ++ try { ++ const target = event.target as HTMLFormElement; ++ const description = target.description.value; ++ target.reset(); ++ await createTask({ description }); ++ } catch (err: any) { ++ window.alert("Error: " + err.message); ++ } ++ }; ++ ++ return ( ++
++ ++ ++
++ ); ++}; diff --git a/web/docs/tutorial/patches/06-actions__main-page-create-task-use-form.patch b/web/docs/tutorial/patches/06-actions__main-page-create-task-use-form.patch new file mode 100644 index 0000000000..2f6b8b3dea --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__main-page-create-task-use-form.patch @@ -0,0 +1,12 @@ +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index eb804b9..cd11965 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -8,6 +8,7 @@ export const MainPage = () => { + + return ( +
++ + {tasks && } + + {isLoading && "Loading..."} diff --git a/web/docs/tutorial/patches/06-actions__main-page-update-task.patch b/web/docs/tutorial/patches/06-actions__main-page-update-task.patch new file mode 100644 index 0000000000..de0210db83 --- /dev/null +++ b/web/docs/tutorial/patches/06-actions__main-page-update-task.patch @@ -0,0 +1,44 @@ +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index 44c52c8..ec559d7 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -1,5 +1,10 @@ +-import type { FormEvent } from "react"; +-import { createTask, getTasks, useQuery } from "wasp/client/operations"; ++import type { ChangeEvent, FormEvent } from "react"; ++import { ++ createTask, ++ getTasks, ++ updateTask, ++ useQuery, ++} from "wasp/client/operations"; + import type { Task } from "wasp/entities"; + + export const MainPage = () => { +@@ -17,9 +22,25 @@ export const MainPage = () => { + }; + + const TaskView = ({ task }: { task: Task }) => { ++ const handleIsDoneChange = async (event: ChangeEvent) => { ++ try { ++ await updateTask({ ++ id: task.id, ++ isDone: event.target.checked, ++ }); ++ } catch (error: any) { ++ window.alert("Error while updating task: " + error.message); ++ } ++ }; ++ + return ( +
+- ++ + {task.description} +
+ ); diff --git a/web/docs/tutorial/patches/07-auth__action-add-auth.patch b/web/docs/tutorial/patches/07-auth__action-add-auth.patch new file mode 100644 index 0000000000..307737476b --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__action-add-auth.patch @@ -0,0 +1,49 @@ +diff --git a/src/actions.ts b/src/actions.ts +index 973d821..b4a52e8 100644 +--- a/src/actions.ts ++++ b/src/actions.ts +@@ -1,4 +1,5 @@ + import type { Task } from "wasp/entities"; ++import { HttpError } from "wasp/server"; + import type { CreateTask, UpdateTask } from "wasp/server/operations"; + + type CreateTaskPayload = Pick; +@@ -7,21 +8,28 @@ export const createTask: CreateTask = async ( + args, + context, + ) => { ++ if (!context.user) { ++ throw new HttpError(401); ++ } + return context.entities.Task.create({ +- data: { description: args.description }, ++ data: { ++ description: args.description, ++ user: { connect: { id: context.user.id } }, ++ }, + }); + }; + + type UpdateTaskPayload = Pick; + +-export const updateTask: UpdateTask = async ( +- { id, isDone }, +- context, +-) => { +- return context.entities.Task.update({ +- where: { id }, +- data: { +- isDone: isDone, +- }, ++export const updateTask: UpdateTask< ++ UpdateTaskPayload, ++ { count: number } ++> = async (args, context) => { ++ if (!context.user) { ++ throw new HttpError(401); ++ } ++ return context.entities.Task.updateMany({ ++ where: { id: args.id, user: { id: context.user.id } }, ++ data: { isDone: args.isDone }, + }); + }; diff --git a/web/docs/tutorial/patches/07-auth__login-page-initial.patch b/web/docs/tutorial/patches/07-auth__login-page-initial.patch new file mode 100644 index 0000000000..10251db19d --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__login-page-initial.patch @@ -0,0 +1,20 @@ +diff --git a/src/LoginPage.tsx b/src/LoginPage.tsx +new file mode 100644 +index 0000000..e2bfb6d +--- /dev/null ++++ b/src/LoginPage.tsx +@@ -0,0 +1,14 @@ ++import { Link } from "react-router-dom"; ++import { LoginForm } from "wasp/client/auth"; ++ ++export const LoginPage = () => { ++ return ( ++
++ ++
++ ++ I don't have an account yet (go to signup). ++ ++
++ ); ++}; diff --git a/web/docs/tutorial/patches/07-auth__main-page-add-auth.patch b/web/docs/tutorial/patches/07-auth__main-page-add-auth.patch new file mode 100644 index 0000000000..301f9f5fda --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__main-page-add-auth.patch @@ -0,0 +1,19 @@ +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index ec559d7..bc435c6 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -1,4 +1,5 @@ + import type { ChangeEvent, FormEvent } from "react"; ++import type { AuthUser } from "wasp/auth"; + import { + createTask, + getTasks, +@@ -7,7 +8,7 @@ import { + } from "wasp/client/operations"; + import type { Task } from "wasp/entities"; + +-export const MainPage = () => { ++export const MainPage = ({ user }: { user: AuthUser }) => { + const { data: tasks, isLoading, error } = useQuery(getTasks); + + return ( diff --git a/web/docs/tutorial/patches/07-auth__main-page-add-logout.patch b/web/docs/tutorial/patches/07-auth__main-page-add-logout.patch new file mode 100644 index 0000000000..c498325864 --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__main-page-add-logout.patch @@ -0,0 +1,19 @@ +diff --git a/src/MainPage.tsx b/src/MainPage.tsx +index bc435c6..b93386a 100644 +--- a/src/MainPage.tsx ++++ b/src/MainPage.tsx +@@ -1,5 +1,6 @@ + import type { ChangeEvent, FormEvent } from "react"; + import type { AuthUser } from "wasp/auth"; ++import { logout } from "wasp/client/auth"; + import { + createTask, + getTasks, +@@ -18,6 +19,7 @@ export const MainPage = ({ user }: { user: AuthUser }) => { + + {isLoading && "Loading..."} + {error && "Error: " + error} ++ +
+ ); + }; diff --git a/web/docs/tutorial/patches/07-auth__prisma-connect-task-user.patch b/web/docs/tutorial/patches/07-auth__prisma-connect-task-user.patch new file mode 100644 index 0000000000..fd50fa7d72 --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__prisma-connect-task-user.patch @@ -0,0 +1,24 @@ +diff --git a/schema.prisma b/schema.prisma +index 76c3741..aad0a3c 100644 +--- a/schema.prisma ++++ b/schema.prisma +@@ -9,12 +9,15 @@ generator client { + provider = "prisma-client-js" + } + ++model User { ++ id Int @id @default(autoincrement()) ++ tasks Task[] ++} ++ + model Task { + id Int @id @default(autoincrement()) + description String + isDone Boolean @default(false) +-} +- +-model User { +- id Int @id @default(autoincrement()) ++ user User? @relation(fields: [userId], references: [id]) ++ userId Int? + } diff --git a/web/docs/tutorial/patches/07-auth__prisma-user.patch b/web/docs/tutorial/patches/07-auth__prisma-user.patch new file mode 100644 index 0000000000..0966ebfcd3 --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__prisma-user.patch @@ -0,0 +1,12 @@ +diff --git a/schema.prisma b/schema.prisma +index 65d7d30..76c3741 100644 +--- a/schema.prisma ++++ b/schema.prisma +@@ -14,3 +14,7 @@ model Task { + description String + isDone Boolean @default(false) + } ++ ++model User { ++ id Int @id @default(autoincrement()) ++} diff --git a/web/docs/tutorial/patches/07-auth__query-add-auth.patch b/web/docs/tutorial/patches/07-auth__query-add-auth.patch new file mode 100644 index 0000000000..58918dcded --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__query-add-auth.patch @@ -0,0 +1,18 @@ +diff --git a/src/queries.ts b/src/queries.ts +index 4e4ba1b..92a513e 100644 +--- a/src/queries.ts ++++ b/src/queries.ts +@@ -1,8 +1,13 @@ + import type { Task } from "wasp/entities"; ++import { HttpError } from "wasp/server"; + import type { GetTasks } from "wasp/server/operations"; + + export const getTasks: GetTasks = async (args, context) => { ++ if (!context.user) { ++ throw new HttpError(401); ++ } + return context.entities.Task.findMany({ ++ where: { user: { id: context.user.id } }, + orderBy: { id: "asc" }, + }); + }; diff --git a/web/docs/tutorial/patches/07-auth__signup-page-initial.patch b/web/docs/tutorial/patches/07-auth__signup-page-initial.patch new file mode 100644 index 0000000000..8f87fcf87c --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__signup-page-initial.patch @@ -0,0 +1,20 @@ +diff --git a/src/SignupPage.tsx b/src/SignupPage.tsx +new file mode 100644 +index 0000000..ee62723 +--- /dev/null ++++ b/src/SignupPage.tsx +@@ -0,0 +1,14 @@ ++import { Link } from "react-router-dom"; ++import { SignupForm } from "wasp/client/auth"; ++ ++export const SignupPage = () => { ++ return ( ++
++ ++
++ ++ I already have an account (go to login). ++ ++
++ ); ++}; diff --git a/web/docs/tutorial/patches/07-auth__wasp-file-auth-required.patch b/web/docs/tutorial/patches/07-auth__wasp-file-auth-required.patch new file mode 100644 index 0000000000..458b674c84 --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__wasp-file-auth-required.patch @@ -0,0 +1,12 @@ +diff --git a/main.wasp b/main.wasp +index f2aa7ca..aa5eef1 100644 +--- a/main.wasp ++++ b/main.wasp +@@ -17,6 +17,7 @@ app TodoApp { + + route RootRoute { path: "/", to: MainPage } + page MainPage { ++ authRequired: true, + component: import { MainPage } from "@src/MainPage" + } + diff --git a/web/docs/tutorial/patches/07-auth__wasp-file-auth-routes.patch b/web/docs/tutorial/patches/07-auth__wasp-file-auth-routes.patch new file mode 100644 index 0000000000..07d56c73b3 --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__wasp-file-auth-routes.patch @@ -0,0 +1,18 @@ +diff --git a/main.wasp b/main.wasp +index 5592563..f2aa7ca 100644 +--- a/main.wasp ++++ b/main.wasp +@@ -39,3 +39,13 @@ action updateTask { + fn: import { updateTask } from "@src/actions", + entities: [Task] + } ++ ++route SignupRoute { path: "/signup", to: SignupPage } ++page SignupPage { ++ component: import { SignupPage } from "@src/SignupPage" ++} ++ ++route LoginRoute { path: "/login", to: LoginPage } ++page LoginPage { ++ component: import { LoginPage } from "@src/LoginPage" ++} diff --git a/web/docs/tutorial/patches/07-auth__wasp-file-auth.patch b/web/docs/tutorial/patches/07-auth__wasp-file-auth.patch new file mode 100644 index 0000000000..38b0ae4d48 --- /dev/null +++ b/web/docs/tutorial/patches/07-auth__wasp-file-auth.patch @@ -0,0 +1,23 @@ +diff --git a/main.wasp b/main.wasp +index f761afe..d4798a4 100644 +--- a/main.wasp ++++ b/main.wasp +@@ -5,7 +5,17 @@ app TodoApp { + title: "TodoApp", + head: [ + "", +- ] ++ ], ++ auth: { ++ // Tells Wasp which entity to use for storing users. ++ userEntity: User, ++ methods: { ++ // Enable username and password auth. ++ usernameAndPassword: {} ++ }, ++ // We'll see how this is used in a bit. ++ onAuthFailedRedirectTo: "/login" ++ } + } + + route RootRoute { path: "/", to: MainPage } diff --git a/web/tutorial-actions-executor/.gitignore b/web/tutorial-actions-executor/.gitignore new file mode 100644 index 0000000000..f7ed446125 --- /dev/null +++ b/web/tutorial-actions-executor/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.result/ diff --git a/web/tutorial-actions-executor/README.md b/web/tutorial-actions-executor/README.md new file mode 100644 index 0000000000..55d9d8480b --- /dev/null +++ b/web/tutorial-actions-executor/README.md @@ -0,0 +1,102 @@ +# Tutorial Actions Executor (tacte) + +## What is this CLI? + +Wasp docs have a tutorial that walks users through building a complete Wasp application step-by-step. + +Next to the text that explains each step, we added `` components that define machine-executable steps, +like "create a new Wasp app", "add authentication", "create a Task entity", etc. +This CLI tool reads those tutorial files, extracts the actions, and executes them in sequence +to create a fully functional Wasp application. + +## Commands + +The CLI provides three commands: + +### 1. Generate App (`npm run generate-app`) + +Creates a complete Wasp application by executing all tutorial actions in sequence. + +```bash +npm run generate-app +# Optional: pass a custom Wasp CLI binary/command +npm run generate-app -- --wasp-cli-command wasp +``` + +This command: + +- Reads all tutorial files (numbered like `01-setup.md`, `02-auth.md`, etc.) +- Extracts `` components from the file +- Applies each action in sequence (e.g. initialize app, apply patches, migrate DB) +- Results in a fully functional application + +One of the action types is to apply a Git patch that modifies a source file. If applying +a patch fails due to conflicts, `generate-app` command pauses and allows you +to resolve the conflicts manually. + +### 2. Edit Patch Action (`npm run edit-patch-action`) + +Allows you to modify a specific patch action and automatically reapplies all subsequent actions. + +```bash +# Non-interactive (direct by ID): +npm run edit-patch-action -- --action-id "create-task-entity" + +# Interactive (pick an action from a list): +npm run edit-patch-action + +# Optional flags: +# - skip generating app before editing +npm run edit-patch-action -- --skip-generating-app +# - pass a custom Wasp CLI +npm run edit-patch-action -- --wasp-cli-command wasp +``` + +This command: + +- Executes all actions before the target action +- Moves all the changes from the target action to the Git staging area +- Allows you to edit the code in your editor +- Updates the patch based on your changes +- Reapplies all subsequent actions + +### 3. List Actions (`npm run list-actions`) + +Displays all available tutorial actions organized by source file. + +```bash +npm run list-actions +``` + +Shows actions grouped by tutorial file, including each action's `id` and `kind`. + +### Patch File Management + +- Patch files are stored in `./docs/tutorial/patches/` +- Files are named using the source file and action ID +- Each patch file contains a Git diff for that specific action + +### Tutorial File Format + +Tutorial actions are defined in MDX files using JSX components: + +````mdx +# Step 4: Create Task Entity + +In this action, we'll create the Task entity: + + +```prisma +model Task { + id Int @id @default(autoincrement()) +} +``` + +```` + +The `` component should wrap the part of the tutorial text that it is associated with. + +The tool extracts these components and uses: + +- `id`: Unique identifier for the action (becomes commit message) +- `action`: Type of action (`INIT_APP`, `APPLY_PATCH`, `MIGRATE_DB`) diff --git a/web/tutorial-actions-executor/package-lock.json b/web/tutorial-actions-executor/package-lock.json new file mode 100644 index 0000000000..6c7d639304 --- /dev/null +++ b/web/tutorial-actions-executor/package-lock.json @@ -0,0 +1,3355 @@ +{ + "name": "tutorial-app-generator", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tutorial-app-generator", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^14.0.0", + "@inquirer/prompts": "^7.8.0", + "acorn": "8.15.0", + "commander": "^14.0.0", + "dedent": "^1.6.0", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-frontmatter": "^2.0.1", + "mdast-util-mdx": "^3.0.0", + "mdast-util-mdx-jsx": "^3.2.0", + "mdast-util-to-markdown": "^2.1.2", + "micromark-extension-mdx-jsx": "^3.0.2", + "parse-git-diff": "0.0.19", + "remark-comment": "^1.0.0", + "tsx": "^4.20.3", + "unist-util-visit": "^5.0.0", + "zx": "^8.5.3" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/mdast": "^4.0.4", + "vitest": "^3.2.4" + } + }, + "node_modules/@commander-js/extra-typings": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-14.0.0.tgz", + "integrity": "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg==", + "license": "MIT", + "peerDependencies": { + "commander": "~14.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", + "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.15.tgz", + "integrity": "sha512-wst31XT8DnGOSS4nNJDIklGKnf+8shuauVrWzgKegWUe28zfCftcWZ2vktGdzJgcylWSS2SrDnYUb6alZcwnCQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", + "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", + "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", + "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", + "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.0.tgz", + "integrity": "sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.2.0", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.15", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", + "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", + "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", + "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", + "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", + "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", + "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", + "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", + "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", + "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", + "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", + "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", + "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", + "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", + "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", + "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", + "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", + "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", + "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", + "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", + "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", + "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", + "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", + "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", + "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", + "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "license": "MIT" + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-git-diff": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/parse-git-diff/-/parse-git-diff-0.0.19.tgz", + "integrity": "sha512-oh3giwKzsPlOhekiDDyd/pfFKn04IZoTjEThquhfKigwiUHymiP/Tp6AN5nGIwXQdWuBTQvz9AaRdN5TBsJ8MA==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-k8YPo5MGvl8l4gGxOH6Zk4Fa2AhDACN5eqKnKZcHDORZQS15hlnezlBHj2lqyDiqzApNmYOMTibkEJbMSKU25w==", + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" + } + }, + "node_modules/remark-comment/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/remark-comment/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/remark-comment/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-comment/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", + "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.3", + "@rollup/rollup-android-arm64": "4.52.3", + "@rollup/rollup-darwin-arm64": "4.52.3", + "@rollup/rollup-darwin-x64": "4.52.3", + "@rollup/rollup-freebsd-arm64": "4.52.3", + "@rollup/rollup-freebsd-x64": "4.52.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", + "@rollup/rollup-linux-arm-musleabihf": "4.52.3", + "@rollup/rollup-linux-arm64-gnu": "4.52.3", + "@rollup/rollup-linux-arm64-musl": "4.52.3", + "@rollup/rollup-linux-loong64-gnu": "4.52.3", + "@rollup/rollup-linux-ppc64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-musl": "4.52.3", + "@rollup/rollup-linux-s390x-gnu": "4.52.3", + "@rollup/rollup-linux-x64-gnu": "4.52.3", + "@rollup/rollup-linux-x64-musl": "4.52.3", + "@rollup/rollup-openharmony-arm64": "4.52.3", + "@rollup/rollup-win32-arm64-msvc": "4.52.3", + "@rollup/rollup-win32-ia32-msvc": "4.52.3", + "@rollup/rollup-win32-x64-gnu": "4.52.3", + "@rollup/rollup-win32-x64-msvc": "4.52.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tsx": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", + "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/zx": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/zx/-/zx-8.7.1.tgz", + "integrity": "sha512-28u1w2LlIfvyvJvYe6pmCipesk8oL5AFMVp+P/U445LcaPgzrU5lNDtAPd6nJvWmoCNyXZz37R/xKOGokccjsw==", + "license": "Apache-2.0", + "bin": { + "zx": "build/cli.js" + }, + "engines": { + "node": ">= 12.17.0" + } + } + } +} diff --git a/web/tutorial-actions-executor/package.json b/web/tutorial-actions-executor/package.json new file mode 100644 index 0000000000..3f6b1579f5 --- /dev/null +++ b/web/tutorial-actions-executor/package.json @@ -0,0 +1,35 @@ +{ + "name": "tutorial-app-generator", + "version": "0.0.1", + "type": "module", + "license": "MIT", + "scripts": { + "edit-patch-action": "tsx ./src/index.ts edit-patch-action", + "generate-app": "tsx ./src/index.ts generate-app", + "list-actions": "tsx ./src/index.ts list-actions", + "test": "vitest run" + }, + "dependencies": { + "@commander-js/extra-typings": "^14.0.0", + "@inquirer/prompts": "^7.8.0", + "acorn": "8.15.0", + "commander": "^14.0.0", + "dedent": "^1.6.0", + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-frontmatter": "^2.0.1", + "mdast-util-mdx": "^3.0.0", + "mdast-util-mdx-jsx": "^3.2.0", + "mdast-util-to-markdown": "^2.1.2", + "micromark-extension-mdx-jsx": "^3.0.2", + "parse-git-diff": "0.0.19", + "remark-comment": "^1.0.0", + "tsx": "^4.20.3", + "unist-util-visit": "^5.0.0", + "zx": "^8.5.3" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/mdast": "^4.0.4", + "vitest": "^3.2.4" + } +} diff --git a/web/tutorial-actions-executor/src/actions/actions.ts b/web/tutorial-actions-executor/src/actions/actions.ts new file mode 100644 index 0000000000..9928416231 --- /dev/null +++ b/web/tutorial-actions-executor/src/actions/actions.ts @@ -0,0 +1,28 @@ +import type { Branded } from "../brandedTypes"; + +// If you modify or add new action kinds, make sure to also update the type in `web/docs/tutorial/TutorialAction.tsx`. +export type Action = InitAppAction | ApplyPatchAction | MigrateDbAction; + +export interface InitAppAction extends BaseAction { + kind: "INIT_APP"; + waspStarterTemplateName: string; +} + +export interface ApplyPatchAction extends BaseAction { + kind: "APPLY_PATCH"; + displayName: string; + patchFilePath: PatchFilePath; +} + +export interface MigrateDbAction extends BaseAction { + kind: "MIGRATE_DB"; +} + +export interface BaseAction { + id: ActionId; + sourceTutorialFilePath: MdxFilePath; +} + +export type ActionId = Branded; +export type PatchFilePath = Branded; +export type MdxFilePath = Branded; diff --git a/web/tutorial-actions-executor/src/actions/git.ts b/web/tutorial-actions-executor/src/actions/git.ts new file mode 100644 index 0000000000..8e78d91fdb --- /dev/null +++ b/web/tutorial-actions-executor/src/actions/git.ts @@ -0,0 +1,118 @@ +import { confirm } from "@inquirer/prompts"; +import parseGitDiff from "parse-git-diff"; +import { fs } from "zx"; + +import { askToOpenTutorialAppInEditor } from "../editor"; +import { + applyPatch, + commitAllChanges, + createBranchFromRevision, + findCommitSHAForExactMessage, + generatePatchFromAllChanges, + generatePatchFromRevision, +} from "../git"; +import { log } from "../log"; +import type { AppDirPath } from "../tutorialApp"; +import type { Action, ApplyPatchAction } from "./actions"; + +export function commitActionChanges({ + appDir, + action, +}: { + appDir: AppDirPath; + action: Action; +}) { + return commitAllChanges(appDir, action.id); +} + +export function getActionCommitSHA({ + appDir, + action, +}: { + appDir: AppDirPath; + action: Action; +}): Promise { + return findCommitSHAForExactMessage(appDir, action.id); +} + +export async function generatePatchForAction({ + appDir, + action, +}: { + appDir: AppDirPath; + action: ApplyPatchAction; +}): Promise { + const actionCommitSha = await getActionCommitSHA({ appDir, action }); + return generatePatchFromRevision(appDir, actionCommitSha); +} + +export async function applyPatchForAction({ + appDir, + action, +}: { + appDir: AppDirPath; + action: ApplyPatchAction; +}): Promise { + await applyPatch(appDir, action.patchFilePath); +} + +export async function regeneratePatchForAction({ + appDir, + action, +}: { + appDir: AppDirPath; + action: ApplyPatchAction; +}): Promise { + log("info", `Trying to fix patch for action: ${action.displayName}`); + + if (await fs.pathExists(action.patchFilePath)) { + log("info", `Removing existing patch file: ${action.patchFilePath}`); + await fs.unlink(action.patchFilePath); + } + + await askUserToEditAndCreatePatch({ appDir, action }); +} + +export async function askUserToEditAndCreatePatch({ + appDir, + action, +}: { + appDir: AppDirPath; + action: ApplyPatchAction; +}) { + await askToOpenTutorialAppInEditor(appDir); + await confirm({ + message: `Update the app according to action ${action.displayName} and press Enter`, + }); + + const patch = await generatePatchFromAllChanges(appDir); + assertValidPatch(patch); + await fs.writeFile(action.patchFilePath, patch, "utf-8"); + + log("success", `Patch file created: ${action.patchFilePath}`); +} + +export async function assertValidPatch(patch: string): Promise { + const parsedPatch = parseGitDiff(patch); + + if (parsedPatch.files.length === 0 || parsedPatch.files[0] === undefined) { + throw new Error("Invalid patch: no changes found"); + } +} + +export async function createBranchFromActionCommit({ + appDir, + branchName, + action, +}: { + appDir: AppDirPath; + branchName: string; + action: ApplyPatchAction; +}): Promise { + const actionCommitSha = await getActionCommitSHA({ appDir, action }); + await createBranchFromRevision({ + gitRepoDirPath: appDir, + branchName, + revision: actionCommitSha, + }); +} diff --git a/web/tutorial-actions-executor/src/actions/index.ts b/web/tutorial-actions-executor/src/actions/index.ts new file mode 100644 index 0000000000..16c22c666b --- /dev/null +++ b/web/tutorial-actions-executor/src/actions/index.ts @@ -0,0 +1,54 @@ +import path, { basename } from "path"; + +import { getFileNameWithoutExtension } from "../files"; +import type { PatchesDirPath } from "../tutorialApp"; +import type { + ActionId, + ApplyPatchAction, + BaseAction, + InitAppAction, + MdxFilePath, + MigrateDbAction, + PatchFilePath, +} from "./actions"; + +export function createInitAppAction( + commonData: BaseAction, + waspStarterTemplateName: string, +): InitAppAction { + return { + ...commonData, + kind: "INIT_APP", + waspStarterTemplateName, + }; +} + +export function createMigrateDbAction(commonData: BaseAction): MigrateDbAction { + return { + ...commonData, + kind: "MIGRATE_DB", + }; +} + +export function createApplyPatchAction( + commonData: BaseAction, + patchesDirPath: PatchesDirPath, +): ApplyPatchAction { + const patchFilename = getPatchFilename( + commonData.id, + commonData.sourceTutorialFilePath, + ); + return { + ...commonData, + kind: "APPLY_PATCH", + displayName: `${basename(commonData.sourceTutorialFilePath)} / ${commonData.id}`, + patchFilePath: path.resolve(patchesDirPath, patchFilename) as PatchFilePath, + }; +} + +function getPatchFilename( + id: ActionId, + sourceTutorialFilePath: MdxFilePath, +): string { + return `${getFileNameWithoutExtension(sourceTutorialFilePath)}__${id}.patch`; +} diff --git a/web/tutorial-actions-executor/src/actions/init.ts b/web/tutorial-actions-executor/src/actions/init.ts new file mode 100644 index 0000000000..297afb1270 --- /dev/null +++ b/web/tutorial-actions-executor/src/actions/init.ts @@ -0,0 +1,35 @@ +import { fs } from "zx"; + +import { initGitRepo } from "../git"; +import { log } from "../log"; +import { + type AppDirPath, + type AppName, + type AppParentDirPath, +} from "../tutorialApp"; +import { waspNew, type WaspCliCommand } from "../waspCli"; + +export async function initWaspAppWithGitRepo({ + waspCliCommand, + appName, + appDirPath, + appParentDirPath, + mainBranchName, +}: { + waspCliCommand: WaspCliCommand; + appName: AppName; + appParentDirPath: AppParentDirPath; + appDirPath: AppDirPath; + mainBranchName: string; +}): Promise { + await fs.ensureDir(appParentDirPath); + await fs.remove(appDirPath); + + await waspNew({ + waspCliCommand, + appName, + appParentDirPath, + }); + await initGitRepo(appDirPath, mainBranchName); + log("info", `Tutorial app has been initialized in ${appDirPath}`); +} diff --git a/web/tutorial-actions-executor/src/assert.ts b/web/tutorial-actions-executor/src/assert.ts new file mode 100644 index 0000000000..0432db3aa3 --- /dev/null +++ b/web/tutorial-actions-executor/src/assert.ts @@ -0,0 +1,3 @@ +export function assertUnreachable(_x: never, message: string): never { + throw new Error(message); +} diff --git a/web/tutorial-actions-executor/src/brandedTypes.ts b/web/tutorial-actions-executor/src/brandedTypes.ts new file mode 100644 index 0000000000..3e9401f1fb --- /dev/null +++ b/web/tutorial-actions-executor/src/brandedTypes.ts @@ -0,0 +1,3 @@ +declare const __brand: unique symbol; +type Brand = { [__brand]: B }; +export type Branded = T & Brand; diff --git a/web/tutorial-actions-executor/src/commands/commonOptions.ts b/web/tutorial-actions-executor/src/commands/commonOptions.ts new file mode 100644 index 0000000000..7a55debd4e --- /dev/null +++ b/web/tutorial-actions-executor/src/commands/commonOptions.ts @@ -0,0 +1,10 @@ +import { Option } from "@commander-js/extra-typings"; + +import type { WaspCliCommand } from "../waspCli"; + +export const waspCliCommandOption = new Option( + "--wasp-cli-command ", + "Wasp CLI command to use", +) + .default("wasp" as WaspCliCommand) + .argParser((value) => value as WaspCliCommand); diff --git a/web/tutorial-actions-executor/src/commands/edit-patch-action/index.ts b/web/tutorial-actions-executor/src/commands/edit-patch-action/index.ts new file mode 100644 index 0000000000..5fb6265eef --- /dev/null +++ b/web/tutorial-actions-executor/src/commands/edit-patch-action/index.ts @@ -0,0 +1,157 @@ +import fs from "fs/promises"; + +import { Command, Option } from "@commander-js/extra-typings"; +import { ProcessOutput } from "zx"; + +import { confirm, select } from "@inquirer/prompts"; +import type { Action, ApplyPatchAction } from "../../actions/actions"; +import { + applyPatchForAction, + askUserToEditAndCreatePatch, + commitActionChanges, + createBranchFromActionCommit, + generatePatchForAction, +} from "../../actions/git"; +import { getActionsFromTutorialFiles } from "../../extract-actions"; +import { + mainBranchName, + moveLastCommitChangesToStaging, + rebaseBranch, +} from "../../git"; +import { log } from "../../log"; +import { tutorialApp, type AppDirPath } from "../../tutorialApp"; +import type { WaspCliCommand } from "../../waspCli"; +import { waspCliCommandOption } from "../commonOptions"; +import { generateApp } from "../generate-app"; + +export const editPatchActionCommand = new Command("edit-patch-action") + .description("Edit a patch action in the tutorial app") + .addOption(new Option("--action-id ", "ID of the action to edit")) + .addOption( + new Option( + "--skip-generating-app", + "Skip generating app before editing action", + ), + ) + .addOption(waspCliCommandOption) + .action(async ({ actionId, skipGeneratingApp, waspCliCommand }) => { + const actions = await getActionsFromTutorialFiles(tutorialApp); + log("info", `Found ${actions.length} actions in tutorial files.`); + + const action = await ensureAction({ + actions, + actionIdOptionValue: actionId, + }); + + if (!skipGeneratingApp) { + log("info", "Generating app before editing action..."); + await generateApp(actions, waspCliCommand as WaspCliCommand); + } else { + log( + "info", + `Skipping app generation, using existing app in ${tutorialApp.dirPath}`, + ); + } + + log("info", `Editing action ${action.displayName}...`); + + await editPatchActionPatch({ appDir: tutorialApp.dirPath, action }); + + await extractCommitsIntoPatches(actions); + + log("success", `Edit completed for action ${action.displayName}!`); + }); + +async function editPatchActionPatch({ + appDir, + action, +}: { + appDir: AppDirPath; + action: ApplyPatchAction; +}): Promise { + const fixesBranchName = "fixes"; + + await createBranchFromActionCommit({ + appDir, + branchName: fixesBranchName, + action, + }); + + await moveLastCommitChangesToStaging(appDir); + await askUserToEditAndCreatePatch({ appDir, action }); + await applyPatchForAction({ appDir, action }); + await commitActionChanges({ appDir, action }); + + try { + await rebaseBranch({ + gitRepoDirPath: appDir, + branchName: fixesBranchName, + baseBranchName: mainBranchName, + }); + } catch (error: unknown) { + if ( + error instanceof ProcessOutput && + error.stderr.includes("git rebase --continue") + ) { + await confirm({ + message: `Resolve rebase issues and press Enter to continue`, + }); + } else { + throw error; + } + } +} + +async function extractCommitsIntoPatches(actions: Action[]): Promise { + const applyPatchActions = actions.filter( + (action) => action.kind === "APPLY_PATCH", + ); + + for (const action of applyPatchActions) { + log("info", `Updating patch for action ${action.displayName}`); + const patch = await generatePatchForAction({ + appDir: tutorialApp.dirPath, + action, + }); + await fs.writeFile(action.patchFilePath, patch, "utf-8"); + } +} + +async function ensureAction({ + actions, + actionIdOptionValue, +}: { + actions: Action[]; + actionIdOptionValue: string | undefined; +}): Promise { + const applyPatchActions = actions.filter( + (action) => action.kind === "APPLY_PATCH", + ); + + if (!actionIdOptionValue) { + return askUserToSelectAction(applyPatchActions); + } + + const action = applyPatchActions.find((a) => a.id === actionIdOptionValue); + if (!action) { + throw new Error( + `Apply patch action with ID "${actionIdOptionValue}" not found.`, + ); + } + return action; +} + +async function askUserToSelectAction( + actions: ApplyPatchAction[], +): Promise { + const selectedActionId = await select({ + message: "Select a action to edit", + choices: actions.map((action) => ({ + name: action.displayName, + value: action.id, + })), + }); + return actions.find( + (a) => a.kind === "APPLY_PATCH" && a.id === selectedActionId, + )!; +} diff --git a/web/tutorial-actions-executor/src/commands/generate-app/execute-actions.ts b/web/tutorial-actions-executor/src/commands/generate-app/execute-actions.ts new file mode 100644 index 0000000000..e288b32f81 --- /dev/null +++ b/web/tutorial-actions-executor/src/commands/generate-app/execute-actions.ts @@ -0,0 +1,69 @@ +import { chalk, fs, spinner } from "zx"; + +import type { Action } from "../../actions/actions"; +import { + applyPatchForAction, + commitActionChanges, + regeneratePatchForAction, +} from "../../actions/git"; +import { initWaspAppWithGitRepo } from "../../actions/init"; +import { assertUnreachable } from "../../assert"; +import { mainBranchName } from "../../git"; +import { log } from "../../log"; +import type { TutorialApp } from "../../tutorialApp"; +import { waspDbMigrate, type WaspCliCommand } from "../../waspCli"; + +export async function executeActions({ + tutorialApp, + actions, + waspCliCommand, +}: { + tutorialApp: TutorialApp; + actions: Action[]; + waspCliCommand: WaspCliCommand; +}): Promise { + for (const action of actions) { + log("info", `${chalk.bold(`[action ${action.id}]`)} ${action.kind}`); + + await fs.ensureDir(tutorialApp.docsTutorialPatchesPath); + + switch (action.kind) { + case "INIT_APP": + await spinner("Initializing the tutorial app...", () => + initWaspAppWithGitRepo({ + waspCliCommand, + appName: tutorialApp.name, + appParentDirPath: tutorialApp.parentDirPath, + appDirPath: tutorialApp.dirPath, + mainBranchName, + }), + ); + break; + case "APPLY_PATCH": + try { + await applyPatchForAction({ appDir: tutorialApp.dirPath, action }); + } catch (err) { + log( + "error", + `Failed to apply patch for action ${action.displayName}:\n${err}`, + ); + await regeneratePatchForAction({ + appDir: tutorialApp.dirPath, + action, + }); + await applyPatchForAction({ appDir: tutorialApp.dirPath, action }); + } + break; + case "MIGRATE_DB": + await waspDbMigrate({ + waspCliCommand, + appDir: tutorialApp.dirPath, + migrationName: action.id, + }); + break; + default: + assertUnreachable(action, `Unknown action '${action}'`); + } + await commitActionChanges({ appDir: tutorialApp.dirPath, action }); + } +} diff --git a/web/tutorial-actions-executor/src/commands/generate-app/index.ts b/web/tutorial-actions-executor/src/commands/generate-app/index.ts new file mode 100644 index 0000000000..f4ac3168c7 --- /dev/null +++ b/web/tutorial-actions-executor/src/commands/generate-app/index.ts @@ -0,0 +1,34 @@ +import { Command } from "@commander-js/extra-typings"; + +import type { Action } from "../../actions/actions"; +import { getActionsFromTutorialFiles } from "../../extract-actions"; +import { log } from "../../log"; +import { tutorialApp } from "../../tutorialApp"; +import type { WaspCliCommand } from "../../waspCli"; +import { waspCliCommandOption } from "../commonOptions"; +import { executeActions } from "./execute-actions"; + +export const generateAppCommand = new Command("generate-app") + .description("Generate a new Wasp app based on the tutorial actions") + .addOption(waspCliCommandOption) + .action(async ({ waspCliCommand }) => { + const actions = await getActionsFromTutorialFiles(tutorialApp); + log("info", `Found ${actions.length} actions in tutorial files.`); + + await generateApp(actions, waspCliCommand); + }); + +export async function generateApp( + actions: Action[], + waspCliCommand: WaspCliCommand, +): Promise { + await executeActions({ + waspCliCommand, + tutorialApp, + actions, + }); + log( + "success", + `Tutorial app has been successfully generated in ${tutorialApp.dirPath}`, + ); +} diff --git a/web/tutorial-actions-executor/src/commands/list-actions/index.ts b/web/tutorial-actions-executor/src/commands/list-actions/index.ts new file mode 100644 index 0000000000..85a23d7e2f --- /dev/null +++ b/web/tutorial-actions-executor/src/commands/list-actions/index.ts @@ -0,0 +1,61 @@ +import { basename } from "path"; + +import { Command } from "@commander-js/extra-typings"; +import { chalk } from "zx"; + +import type { Action } from "../../actions/actions"; +import { getActionsFromTutorialFiles } from "../../extract-actions"; +import { tutorialApp } from "../../tutorialApp"; + +type SourceFileName = string; +type ActionsGroupedByFile = Map; + +export const listActionsCommand = new Command("list-actions") + .description("List all actions in the tutorial") + .action(async () => { + const actions = await getActionsFromTutorialFiles(tutorialApp); + const actionsGroupedByFile = groupActionsBySourceFile(actions); + displayGroupedActions(actionsGroupedByFile); + }); + +export function groupActionsBySourceFile( + actions: Action[], +): ActionsGroupedByFile { + const groupedActions = new Map(); + + for (const action of actions) { + const filename = basename(action.sourceTutorialFilePath); + const existingActions = groupedActions.get(filename) ?? []; + groupedActions.set(filename, [...existingActions, action]); + } + + return groupedActions; +} + +function displayGroupedActions( + actionsGroupedByFile: ActionsGroupedByFile, +): void { + for (const [filename, fileActions] of actionsGroupedByFile) { + displayFileHeader(filename); + displayActionsForFile(fileActions); + console.log(); + } +} + +function displayFileHeader(filename: string): void { + console.log(chalk.bold.magenta(filename)); + console.log(); +} + +function displayActionsForFile(actions: Action[]): void { + const kindColorMap: Record string> = { + INIT_APP: chalk.yellow, + APPLY_PATCH: chalk.green, + MIGRATE_DB: chalk.blue, + }; + + actions.forEach((action) => { + const kindColorFn = kindColorMap[action.kind]; + console.log(`- ${chalk.bold(action.id)} (${kindColorFn(action.kind)})`); + }); +} diff --git a/web/tutorial-actions-executor/src/editor.ts b/web/tutorial-actions-executor/src/editor.ts new file mode 100644 index 0000000000..93e4ed5c20 --- /dev/null +++ b/web/tutorial-actions-executor/src/editor.ts @@ -0,0 +1,19 @@ +import { confirm } from "@inquirer/prompts"; +import { $ } from "zx"; + +import type { AppDirPath } from "./tutorialApp"; + +export async function askToOpenTutorialAppInEditor( + appDirPath: AppDirPath, +): Promise { + const editor = process.env.EDITOR; + if (!editor) { + return; + } + const wantsToOpenVSCode = await confirm({ + message: `Do you want to open the tutorial app in ${editor}? (Found in $EDITOR env variable)`, + }); + if (wantsToOpenVSCode) { + await $`$EDITOR ${appDirPath}`; + } +} diff --git a/web/tutorial-actions-executor/src/extract-actions/index.ts b/web/tutorial-actions-executor/src/extract-actions/index.ts new file mode 100644 index 0000000000..478ae3cbcd --- /dev/null +++ b/web/tutorial-actions-executor/src/extract-actions/index.ts @@ -0,0 +1,166 @@ +import fs from "fs/promises"; +import path from "path"; + +import * as acorn from "acorn"; +import { fromMarkdown } from "mdast-util-from-markdown"; +import { mdxJsxFromMarkdown, type MdxJsxFlowElement } from "mdast-util-mdx-jsx"; +import { mdxJsx } from "micromark-extension-mdx-jsx"; +import { visit } from "unist-util-visit"; + +import type { + Action, + ActionId, + BaseAction, + MdxFilePath, +} from "../actions/actions.js"; +import { + createApplyPatchAction, + createInitAppAction, + createMigrateDbAction, +} from "../actions/index.js"; +import { assertUnreachable } from "../assert.js"; +import type { TutorialApp, TutorialDirPath } from "../tutorialApp.js"; + +export async function getActionsFromTutorialFiles( + tutorialApp: TutorialApp, +): Promise { + const tutorialFilePaths = await getTutorialFilePaths( + tutorialApp.docsTutorialDirPath, + ); + const actions: Action[] = []; + + for (const filePath of tutorialFilePaths) { + const fileActions = await getActionsFromMdxFile(filePath, tutorialApp); + actions.push(...fileActions); + } + + return actions; +} + +async function getTutorialFilePaths( + tutorialDir: TutorialDirPath, +): Promise { + const files = await fs.readdir(tutorialDir); + return sortFileNamesByNumberedPrefix(filterValidTutorialFileNames(files)).map( + (file) => path.resolve(tutorialDir, file) as MdxFilePath, + ); +} + +const SUPPORTED_TUTORIAL_FILE_EXTENSIONS = ["md", "mdx"] as const; + +export function filterValidTutorialFileNames(fileNames: string[]): string[] { + return fileNames.filter((fileName) => { + const lowerFileName = fileName.toLowerCase(); + return SUPPORTED_TUTORIAL_FILE_EXTENSIONS.some((ext) => + lowerFileName.endsWith(`.${ext}`), + ); + }); +} + +/** + * Sorts a list of files which are named "01-something.md" by their numeric prefix. + */ +export function sortFileNamesByNumberedPrefix(fileNames: string[]): string[] { + return fileNames.sort((a, b) => { + const aNumber = parseInt(a.split("-")[0]!, 10); + const bNumber = parseInt(b.split("-")[0]!, 10); + return aNumber - bNumber; + }); +} + +async function getActionsFromMdxFile( + sourceTutorialFilePath: MdxFilePath, + tutorialApp: TutorialApp, +): Promise { + const fileContent = await fs.readFile(path.resolve(sourceTutorialFilePath)); + return getActionsFromMdxContent( + sourceTutorialFilePath, + fileContent, + tutorialApp, + ); +} + +export async function getActionsFromMdxContent( + sourceTutorialFilePath: MdxFilePath, + fileContent: Buffer, + tutorialApp: TutorialApp, +): Promise { + const actions: Action[] = []; + + const ast = fromMarkdown(fileContent, { + extensions: [mdxJsx({ acorn, addResult: true })], + mdastExtensions: [mdxJsxFromMarkdown()], + }); + + const tutorialComponentName = "TutorialAction"; + visit(ast, "mdxJsxFlowElement", (node) => { + if (node.name !== tutorialComponentName) { + return; + } + const actionId = getAttributeValue(node, "id") as ActionId | null; + const actionName = getAttributeValue(node, "action") as + | Action["kind"] + | null; + const waspStarterTemplateName = getAttributeValue( + node, + "starterTemplateName", + ); + + if (!actionId) { + throw new Error( + `TutorialAction component requires the 'id' attribute. File: ${sourceTutorialFilePath}`, + ); + } + + if (!actionName) { + throw new Error( + `TutorialAction component requires the 'action' attribute. File: ${sourceTutorialFilePath}`, + ); + } + + const commonData: BaseAction = { + id: actionId, + sourceTutorialFilePath, + }; + switch (actionName) { + case "INIT_APP": + if (waspStarterTemplateName === null) { + throw new Error( + `TutorialAction with action 'INIT_APP' requires the 'starterTemplateName' attribute. File: ${sourceTutorialFilePath}`, + ); + } + actions.push(createInitAppAction(commonData, waspStarterTemplateName)); + break; + case "APPLY_PATCH": + actions.push( + createApplyPatchAction( + commonData, + tutorialApp.docsTutorialPatchesPath, + ), + ); + break; + case "MIGRATE_DB": + actions.push(createMigrateDbAction(commonData)); + break; + default: + assertUnreachable( + actionName, + `Unknown action '${actionName}' in TutorialAction component. File: ${sourceTutorialFilePath}`, + ); + } + }); + + return actions; +} + +export function getAttributeValue( + node: MdxJsxFlowElement, + attributeName: string, +): string | null { + const attribute = node.attributes.find( + (attr) => attr.type === "mdxJsxAttribute" && attr.name === attributeName, + ); + return attribute && typeof attribute.value === "string" + ? attribute.value + : null; +} diff --git a/web/tutorial-actions-executor/src/files.ts b/web/tutorial-actions-executor/src/files.ts new file mode 100644 index 0000000000..26f52fe037 --- /dev/null +++ b/web/tutorial-actions-executor/src/files.ts @@ -0,0 +1,5 @@ +import path from "path"; + +export function getFileNameWithoutExtension(filePath: string): string { + return path.basename(filePath, path.extname(filePath)); +} diff --git a/web/tutorial-actions-executor/src/git.ts b/web/tutorial-actions-executor/src/git.ts new file mode 100644 index 0000000000..8a30ba91db --- /dev/null +++ b/web/tutorial-actions-executor/src/git.ts @@ -0,0 +1,120 @@ +import { $ } from "zx"; + +export const mainBranchName = "main"; + +export async function generatePatchFromAllChanges( + gitRepoDirPath: string, +): Promise { + await commitAllChanges(gitRepoDirPath, "temporary-commit"); + const patch = await generatePatchFromRevision(gitRepoDirPath, "HEAD"); + await removeLastCommit(gitRepoDirPath); + return patch; +} + +export async function applyPatch(gitRepoDirPath: string, patchPath: string) { + await $({ cwd: gitRepoDirPath })`git apply ${patchPath} --verbose`.quiet( + true, + ); +} + +export async function findCommitSHAForExactMessage( + gitRepoDirPath: string, + message: string, +): Promise { + const commits = await grepGitCommitsByMessage(gitRepoDirPath, message); + + const commit = commits.find((commit) => commit.message === message); + if (!commit) { + throw new Error(`No commit found with message: "${message}"`); + } + + return commit.sha; +} + +interface GitCommit { + message: string; + sha: string; +} + +async function grepGitCommitsByMessage( + gitRepoDirPath: string, + message: string, +): Promise { + const format = `{"message":"%s","sha":"%H"}`; + const { stdout } = await $({ + cwd: gitRepoDirPath, + })`git log --branches --format=${format} --grep=${message}`; + + const commits = stdout.split("\n").filter((line) => line.trim() !== ""); + return commits.map((commit) => { + const parsed = JSON.parse(commit); + return { + message: parsed.message, + sha: parsed.sha, + }; + }); +} + +export async function commitAllChanges( + gitRepoDirPath: string, + message: string, +): Promise { + await $({ cwd: gitRepoDirPath })`git add .`; + await $({ cwd: gitRepoDirPath })`git commit -m ${message}`; +} + +async function removeLastCommit(gitRepoDirPath: string): Promise { + await $({ cwd: gitRepoDirPath })`git reset --hard HEAD~1`; +} + +export async function generatePatchFromRevision( + gitRepoDirPath: string, + gitRevision: string, +): Promise { + const { stdout: patch } = await $({ + cwd: gitRepoDirPath, + })`git show ${gitRevision} --format=`; + + return patch; +} + +export async function initGitRepo( + gitRepoDirPath: string, + mainBranchName: string, +): Promise { + await $({ cwd: gitRepoDirPath })`git init`.quiet(true); + await $({ cwd: gitRepoDirPath })`git branch -m ${mainBranchName}`; +} + +export async function createBranchFromRevision({ + gitRepoDirPath, + branchName, + revision, +}: { + gitRepoDirPath: string; + branchName: string; + revision: string; +}): Promise { + await $({ + cwd: gitRepoDirPath, + })`git switch --force-create ${branchName} ${revision}`; +} + +export async function moveLastCommitChangesToStaging( + gitRepoDirPath: string, +): Promise { + await $({ cwd: gitRepoDirPath })`git reset --soft HEAD~1`; +} + +export async function rebaseBranch({ + gitRepoDirPath, + branchName, + baseBranchName, +}: { + gitRepoDirPath: string; + branchName: string; + baseBranchName: string; +}): Promise { + await $({ cwd: gitRepoDirPath })`git switch ${baseBranchName}`; + await $({ cwd: gitRepoDirPath })`git rebase ${branchName}`; +} diff --git a/web/tutorial-actions-executor/src/index.ts b/web/tutorial-actions-executor/src/index.ts new file mode 100644 index 0000000000..9373f3ffab --- /dev/null +++ b/web/tutorial-actions-executor/src/index.ts @@ -0,0 +1,12 @@ +import { program } from "@commander-js/extra-typings"; + +import { editPatchActionCommand } from "./commands/edit-patch-action"; +import { generateAppCommand } from "./commands/generate-app"; +import { listActionsCommand } from "./commands/list-actions"; + +program + .addCommand(generateAppCommand) + .addCommand(editPatchActionCommand) + .addCommand(listActionsCommand) + .parse(process.argv) + .opts(); diff --git a/web/tutorial-actions-executor/src/log.ts b/web/tutorial-actions-executor/src/log.ts new file mode 100644 index 0000000000..da1e86379e --- /dev/null +++ b/web/tutorial-actions-executor/src/log.ts @@ -0,0 +1,13 @@ +import { chalk } from "zx"; + +const levels = { + info: { color: chalk.blue }, + success: { color: chalk.green }, + error: { color: chalk.red }, +}; + +export function log(level: keyof typeof levels, message: string) { + console.log( + levels[level].color(`[${level.toUpperCase()}] ${chalk.reset(message)}`), + ); +} diff --git a/web/tutorial-actions-executor/src/tutorialApp.ts b/web/tutorial-actions-executor/src/tutorialApp.ts new file mode 100644 index 0000000000..89e7b58ac6 --- /dev/null +++ b/web/tutorial-actions-executor/src/tutorialApp.ts @@ -0,0 +1,31 @@ +import path from "path"; + +import type { Branded } from "./brandedTypes"; + +export type AppName = Branded; +export type AppDirPath = Branded; +export type AppParentDirPath = Branded; +export type TutorialDirPath = Branded; +export type PatchesDirPath = Branded; + +const tutorialAppName = "TodoApp" as AppName; +const tutorialAppParentDirPath = path.resolve("./.result") as AppParentDirPath; +const tutorialAppDirPath = path.resolve( + tutorialAppParentDirPath, + tutorialAppName, +) as AppDirPath; +const docsTutorialDirPath = path.resolve("../docs/tutorial") as TutorialDirPath; +const docsTutorialPatchesPath = path.resolve( + docsTutorialDirPath, + "patches", +) as PatchesDirPath; + +export const tutorialApp = { + name: tutorialAppName, + parentDirPath: tutorialAppParentDirPath, + dirPath: tutorialAppDirPath, + docsTutorialDirPath, + docsTutorialPatchesPath, +} as const; + +export type TutorialApp = typeof tutorialApp; diff --git a/web/tutorial-actions-executor/src/waspCli.ts b/web/tutorial-actions-executor/src/waspCli.ts new file mode 100644 index 0000000000..f43a0126d1 --- /dev/null +++ b/web/tutorial-actions-executor/src/waspCli.ts @@ -0,0 +1,36 @@ +import { $ } from "zx"; + +import type { Branded } from "./brandedTypes"; +import type { AppDirPath, AppName, AppParentDirPath } from "./tutorialApp"; + +export type WaspCliCommand = Branded; + +export async function waspDbMigrate({ + waspCliCommand, + appDir, + migrationName, +}: { + waspCliCommand: WaspCliCommand; + appDir: AppDirPath; + migrationName: string; +}): Promise { + await $({ + // Needs to inhert stdio for `wasp db migrate-dev` to work + stdio: "inherit", + cwd: appDir, + })`${waspCliCommand} db migrate-dev --name ${migrationName}`; +} + +export async function waspNew({ + waspCliCommand, + appName, + appParentDirPath, +}: { + waspCliCommand: WaspCliCommand; + appName: AppName; + appParentDirPath: AppParentDirPath; +}): Promise { + await $({ + cwd: appParentDirPath, + })`${waspCliCommand} new ${appName} -t minimal`; +} diff --git a/web/tutorial-actions-executor/tests/actions/git.fixtures.ts b/web/tutorial-actions-executor/tests/actions/git.fixtures.ts new file mode 100644 index 0000000000..417c6ac1ad --- /dev/null +++ b/web/tutorial-actions-executor/tests/actions/git.fixtures.ts @@ -0,0 +1,56 @@ +export const singleFilePatch = `diff --git a/test.js b/test.js +index 1234567..abcdefg 100644 +--- a/test.js ++++ b/test.js +@@ -1,3 +1,4 @@ + console.log('hello'); ++console.log('world'); + export const x = 1; + export const y = 2;`; + +export const multiFilePatch = `diff --git a/file1.js b/file1.js +index 1234567..abcdefg 100644 +--- a/file1.js ++++ b/file1.js +@@ -1,2 +1,3 @@ + export const a = 1; ++export const b = 2; + export { a }; +diff --git a/file2.js b/file2.js +index 2345678..bcdefgh 100644 +--- a/file2.js ++++ b/file2.js +@@ -1,2 +1,3 @@ + export const c = 3; ++export const d = 4; + export { c };`; + +export const deletionPatch = `diff --git a/deleted-file.js b/deleted-file.js +deleted file mode 100644 +index 1234567..0000000 +--- a/deleted-file.js ++++ /dev/null +@@ -1,3 +0,0 @@ +-console.log('deleted'); +-export const x = 1; +-export { x };`; + +export const additionPatch = `diff --git a/new-file.js b/new-file.js +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/new-file.js +@@ -0,0 +1,3 @@ ++console.log('new file'); ++export const y = 2; ++export { y };`; + +export const binaryPatch = `diff --git a/image.png b/image.png +index 1234567..abcdefg 100644 +Binary files a/image.png and b/image.png differ`; + +export const noFilesPatch = `commit abc123 +Author: Test User +Date: Mon Jan 1 00:00:00 2023 +0000 + + Some commit message`; diff --git a/web/tutorial-actions-executor/tests/actions/git.test.ts b/web/tutorial-actions-executor/tests/actions/git.test.ts new file mode 100644 index 0000000000..cf08a312b9 --- /dev/null +++ b/web/tutorial-actions-executor/tests/actions/git.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; + +import { assertValidPatch } from "../../src/actions/git"; +import { + additionPatch, + binaryPatch, + deletionPatch, + multiFilePatch, + noFilesPatch, + singleFilePatch, +} from "./git.fixtures"; + +describe("assertValidPatch", () => { + expectValidPatchNotToThrow(singleFilePatch); + expectValidPatchNotToThrow(multiFilePatch); + expectValidPatchNotToThrow(deletionPatch); + expectValidPatchNotToThrow(additionPatch); + expectValidPatchNotToThrow(binaryPatch); + + expectInvalidPatchToThrow(noFilesPatch, "Invalid patch: no changes found"); +}); + +function expectValidPatchNotToThrow(patch: string) { + it("should not throw for valid patch", async () => { + await expect(assertValidPatch(patch)).resolves.not.toThrow(); + }); +} + +function expectInvalidPatchToThrow(patch: string, errorMessage: string) { + it(`should throw for invalid patch: ${errorMessage}`, async () => { + await expect(assertValidPatch(patch)).rejects.toThrow(errorMessage); + }); +} diff --git a/web/tutorial-actions-executor/tests/actions/index.test.ts b/web/tutorial-actions-executor/tests/actions/index.test.ts new file mode 100644 index 0000000000..bec7dadbf2 --- /dev/null +++ b/web/tutorial-actions-executor/tests/actions/index.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; + +import type { + ActionId, + BaseAction, + MdxFilePath, +} from "../../src/actions/actions"; +import { + createApplyPatchAction, + createInitAppAction, + createMigrateDbAction, +} from "../../src/actions/index"; +import type { PatchesDirPath } from "../../src/tutorialApp"; + +describe("createInitAppAction", () => { + it("should create InitAppAction with correct properties", () => { + const baseAction: BaseAction = { + id: "test-init" as ActionId, + sourceTutorialFilePath: "/docs/01-intro.md" as MdxFilePath, + }; + + const result = createInitAppAction(baseAction, "basic"); + + expect(result).toEqual({ + id: "test-init", + sourceTutorialFilePath: "/docs/01-intro.md", + kind: "INIT_APP", + waspStarterTemplateName: "basic", + }); + }); +}); + +describe("createMigrateDbAction", () => { + it("should create MigrateDbAction with correct properties", () => { + const baseAction: BaseAction = { + id: "db-migrate" as ActionId, + sourceTutorialFilePath: "/docs/02-database.md" as MdxFilePath, + }; + + const result = createMigrateDbAction(baseAction); + + expect(result).toEqual({ + id: "db-migrate", + sourceTutorialFilePath: "/docs/02-database.md", + kind: "MIGRATE_DB", + }); + }); +}); + +describe("createApplyPatchAction", () => { + it("should create ApplyPatchAction with correct properties", () => { + const baseAction: BaseAction = { + id: "add-auth" as ActionId, + sourceTutorialFilePath: "/docs/03-auth.md" as MdxFilePath, + }; + const patchesDirPath = "/patches" as PatchesDirPath; + + const result = createApplyPatchAction(baseAction, patchesDirPath); + + expect(result).toEqual({ + id: "add-auth", + sourceTutorialFilePath: "/docs/03-auth.md", + kind: "APPLY_PATCH", + displayName: "03-auth.md / add-auth", + patchFilePath: "/patches/03-auth__add-auth.patch", + }); + }); +}); diff --git a/web/tutorial-actions-executor/tests/commands/list-actions.test.ts b/web/tutorial-actions-executor/tests/commands/list-actions.test.ts new file mode 100644 index 0000000000..9b71eee3f1 --- /dev/null +++ b/web/tutorial-actions-executor/tests/commands/list-actions.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; + +import type { Action, ActionId, MdxFilePath } from "../../src/actions/actions"; +import { groupActionsBySourceFile } from "../../src/commands/list-actions/index"; + +describe("groupActionsBySourceFile", () => { + it("should group actions by source file basename", () => { + const actions: Action[] = [ + { + id: "action1" as ActionId, + sourceTutorialFilePath: "/path/to/01-intro.md" as MdxFilePath, + kind: "INIT_APP", + waspStarterTemplateName: "basic", + }, + { + id: "action2" as ActionId, + sourceTutorialFilePath: "/path/to/01-intro.md" as MdxFilePath, + kind: "APPLY_PATCH", + displayName: "Add auth", + patchFilePath: "/patches/auth.patch" as any, + }, + { + id: "action3" as ActionId, + sourceTutorialFilePath: "/path/to/02-setup.md" as MdxFilePath, + kind: "MIGRATE_DB", + }, + ]; + + const result = groupActionsBySourceFile(actions); + + expect(result.size).toBe(2); + expect(result.get("01-intro.md")).toEqual([actions[0], actions[1]]); + expect(result.get("02-setup.md")).toEqual([actions[2]]); + }); + + it("should handle empty actions array", () => { + const actions: Action[] = []; + const result = groupActionsBySourceFile(actions); + expect(result.size).toBe(0); + }); +}); diff --git a/web/tutorial-actions-executor/tests/extract-actions/exampleMdxFiles.ts b/web/tutorial-actions-executor/tests/extract-actions/exampleMdxFiles.ts new file mode 100644 index 0000000000..4fa9daac0c --- /dev/null +++ b/web/tutorial-actions-executor/tests/extract-actions/exampleMdxFiles.ts @@ -0,0 +1,78 @@ +export const mdxWithInitAppAction = ` +# Tutorial + + + +Some content here. +`; + +export const mdxWithApplyPatchAction = ` +# Tutorial + + + +\`\`\` +const someCode = "" +\`\`\` + + +Some content here. +`; + +export const mdxWithMigrateDbAction = ` +# Tutorial + + + +Some content here. +`; + +export const mdxWithMultipleActions = ` +# Tutorial + + + +Some content here. + + +\`\`\` +const someCode = "" +\`\`\` + + +More content. + + +`; + +export const mdxWithNoActions = ` +# Tutorial + +Just some regular content without any tutorial actions. +`; + +export const mdxWithNonTutorialActionComponents = ` +# Tutorial + + + + + + +`; + +export const mdxWithMissingActionAttribute = ` + +`; + +export const mdxWithMissingIdAttribute = ` + +`; + +export const mdxWithMissingStarterTemplateName = ` + +`; + +export const mdxWithUnknownActionType = ` + +`; diff --git a/web/tutorial-actions-executor/tests/extract-actions/index.test.ts b/web/tutorial-actions-executor/tests/extract-actions/index.test.ts new file mode 100644 index 0000000000..af9652e898 --- /dev/null +++ b/web/tutorial-actions-executor/tests/extract-actions/index.test.ts @@ -0,0 +1,241 @@ +import type { MdxJsxAttribute, MdxJsxFlowElement } from "mdast-util-mdx-jsx"; +import { describe, expect, it } from "vitest"; + +import type { MdxFilePath } from "../../src/actions/actions"; +import { + filterValidTutorialFileNames, + getActionsFromMdxContent, + getAttributeValue, + sortFileNamesByNumberedPrefix, +} from "../../src/extract-actions/index"; +import type { + AppDirPath, + AppName, + AppParentDirPath, + PatchesDirPath, + TutorialDirPath, +} from "../../src/tutorialApp"; +import { + mdxWithApplyPatchAction, + mdxWithInitAppAction, + mdxWithMigrateDbAction, + mdxWithMissingActionAttribute, + mdxWithMissingIdAttribute, + mdxWithMissingStarterTemplateName, + mdxWithMultipleActions, + mdxWithNoActions, + mdxWithNonTutorialActionComponents, + mdxWithUnknownActionType, +} from "./exampleMdxFiles"; + +describe("getAttributeValue", () => { + it("should return correct value when attributes exist", () => { + const mockNode = { + attributes: [ + { + type: "mdxJsxAttribute", + name: "action", + value: "APPLY_PATCH", + }, + { + type: "mdxJsxAttribute", + name: "id", + value: "test-action", + }, + ], + } as MdxJsxFlowElement; + + expect(getAttributeValue(mockNode, "id")).toBe("test-action"); + expect(getAttributeValue(mockNode, "action")).toBe("APPLY_PATCH"); + }); + + it("should return null when attribute does not exist", () => { + const mockNode = { + attributes: [] as MdxJsxAttribute[], + } as MdxJsxFlowElement; + + expect(getAttributeValue(mockNode, "id")).toBe(null); + }); +}); + +describe("filterValidTutorialFileNames", () => { + function itShouldFilterTutorialFileNames( + testName: string, + inputFiles: string[], + expectedFiles: string[], + ) { + it(testName, () => { + expect(filterValidTutorialFileNames(inputFiles)).toEqual(expectedFiles); + }); + } + + itShouldFilterTutorialFileNames( + "should return empty array when no markdown files are present", + ["file.txt", "image.png", "document.pdf"], + [], + ); + + itShouldFilterTutorialFileNames( + "should filter files ending with .md or .mdx", + [ + "01-intro.md", + "02-setup.mdx", + "03-advanced.md", + "README.txt", + "package.json", + ], + ["01-intro.md", "02-setup.mdx", "03-advanced.md"], + ); + + itShouldFilterTutorialFileNames( + "should handle case-insensitive file extensions", + ["README.MD", "guide.MDX", "tutorial.Md", "docs.mDx", "file.txt"], + ["README.MD", "guide.MDX", "tutorial.Md", "docs.mDx"], + ); + + itShouldFilterTutorialFileNames( + "should not match extensions in the middle of filename", + ["file.md.txt", "file.mdx.backup", "README.md"], + ["README.md"], + ); +}); + +describe("sortFileNamesByNumberedPrefix", () => { + it("should sort files by numeric prefix", () => { + const files = ["03-advanced.md", "01-intro.md", "02-setup.md"]; + + const result = sortFileNamesByNumberedPrefix(files); + + expect(result).toEqual(["01-intro.md", "02-setup.md", "03-advanced.md"]); + }); +}); + +describe("getActionsFromMdxContent", () => { + const context = { + tutorialApp: { + name: "TestApp" as AppName, + parentDirPath: "/test/parent" as AppParentDirPath, + dirPath: "/test/parent/TestApp" as AppDirPath, + docsTutorialDirPath: "/test/tutorial" as TutorialDirPath, + docsTutorialPatchesPath: "/test/tutorial/patches" as PatchesDirPath, + }, + filePath: "/test/tutorial/01-intro.md" as MdxFilePath, + }; + + function itShouldExtractActions( + testName: string, + mdxContent: string, + expectedActions: unknown[], + ) { + it(`should extract actions: ${testName}`, async () => { + const actions = await getActionsFromMdxContent( + context.filePath, + Buffer.from(mdxContent), + context.tutorialApp, + ); + expect(actions).toEqual(expectedActions); + }); + } + + function itShouldFailWhenExtractingActions( + testName: string, + mdxContent: string, + errorMessage: string, + ) { + it(`should fail extracting actions: ${testName}`, async () => { + await expect( + getActionsFromMdxContent( + context.filePath, + Buffer.from(mdxContent), + context.tutorialApp, + ), + ).rejects.toThrow(errorMessage); + }); + } + + itShouldExtractActions("INIT_APP action", mdxWithInitAppAction, [ + { + id: "init-project", + sourceTutorialFilePath: context.filePath, + kind: "INIT_APP", + waspStarterTemplateName: "basic", + }, + ]); + + itShouldExtractActions("APPLY_PATCH action", mdxWithApplyPatchAction, [ + { + id: "add-feature", + sourceTutorialFilePath: context.filePath, + kind: "APPLY_PATCH", + displayName: "01-intro.md / add-feature", + patchFilePath: "/test/tutorial/patches/01-intro__add-feature.patch", + }, + ]); + + itShouldExtractActions("MIGRATE_DB action", mdxWithMigrateDbAction, [ + { + id: "migrate-schema", + sourceTutorialFilePath: context.filePath, + kind: "MIGRATE_DB", + }, + ]); + + itShouldExtractActions("multiple actions", mdxWithMultipleActions, [ + { + id: "init-project", + sourceTutorialFilePath: context.filePath, + kind: "INIT_APP", + waspStarterTemplateName: "basic", + }, + { + id: "add-feature", + sourceTutorialFilePath: context.filePath, + kind: "APPLY_PATCH", + displayName: "01-intro.md / add-feature", + patchFilePath: "/test/tutorial/patches/01-intro__add-feature.patch", + }, + { + id: "migrate-schema", + sourceTutorialFilePath: context.filePath, + kind: "MIGRATE_DB", + }, + ]); + + itShouldExtractActions("no actions present", mdxWithNoActions, []); + + itShouldExtractActions( + "should ignore non-TutorialAction MDX components", + mdxWithNonTutorialActionComponents, + [ + { + id: "valid-action", + sourceTutorialFilePath: context.filePath, + kind: "MIGRATE_DB", + }, + ], + ); + + itShouldFailWhenExtractingActions( + "when action attribute is missing", + mdxWithMissingActionAttribute, + "TutorialAction component requires the 'action' attribute", + ); + + itShouldFailWhenExtractingActions( + "when id attribute is missing", + mdxWithMissingIdAttribute, + "TutorialAction component requires the 'id' attribute", + ); + + itShouldFailWhenExtractingActions( + "when INIT_APP action is missing starterTemplateName", + mdxWithMissingStarterTemplateName, + "TutorialAction with action 'INIT_APP' requires the 'starterTemplateName' attribute", + ); + + itShouldFailWhenExtractingActions( + "for unknown action type", + mdxWithUnknownActionType, + "Unknown action 'UNKNOWN_ACTION' in TutorialAction component", + ); +}); diff --git a/web/tutorial-actions-executor/tests/files.test.ts b/web/tutorial-actions-executor/tests/files.test.ts new file mode 100644 index 0000000000..e82df04d60 --- /dev/null +++ b/web/tutorial-actions-executor/tests/files.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; + +import { getFileNameWithoutExtension } from "../src/files"; + +describe("getFileNameWithoutExtension", () => { + testGettingFileNameWithoutExtension("test.txt", "test"); + testGettingFileNameWithoutExtension("test.config.js", "test.config"); + testGettingFileNameWithoutExtension("test", "test"); + testGettingFileNameWithoutExtension("/path/to/test.txt", "test"); + testGettingFileNameWithoutExtension("./src/test.ts", "test"); + testGettingFileNameWithoutExtension("", ""); +}); + +function testGettingFileNameWithoutExtension( + filePath: string, + expectedFileName: string, +) { + it(`should return "${expectedFileName}" for "${filePath}"`, () => { + expect(getFileNameWithoutExtension(filePath)).toBe(expectedFileName); + }); +} diff --git a/web/tutorial-actions-executor/tsconfig.json b/web/tutorial-actions-executor/tsconfig.json new file mode 100644 index 0000000000..589c7f2011 --- /dev/null +++ b/web/tutorial-actions-executor/tsconfig.json @@ -0,0 +1,22 @@ +// Based on https://www.totaltypescript.com/tsconfig-cheat-sheet +{ + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + "module": "preserve", + "noEmit": true, + + "lib": ["es2022"] + } +} diff --git a/web/tutorial-actions-executor/vitest.config.ts b/web/tutorial-actions-executor/vitest.config.ts new file mode 100644 index 0000000000..ba29bd1c7a --- /dev/null +++ b/web/tutorial-actions-executor/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["tests/**/*.test.ts"], + environment: "node", + }, +});