diff --git a/README.md b/README.md index 74c3f507..483c9793 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ yarn moker add --template bandersnatch cli - [`express`](#express) - [`github-action`](#github-action) - [`lib`](#lib) + - [`next`](#next) + - [`sanity`](#sanity) - [Commands](#commands) - [Contributing](#contributing) - [Roadmap](#roadmap) @@ -451,6 +453,21 @@ _Scope: repo or workspace_ A plain shared library template with the [typescript](#typescript-workspace) and [jest](#jest-workspace) plugins. +## `next` + +_Scope: repo or workspace_ + +Uses +[create-next-app](https://nextjs.org/docs/pages/api-reference/create-next-app) +to scaffold a Next.js app. + +## `sanity` + +_Scope: repo or workspace_ + +Uses [create-sanity](https://www.sanity.io/docs/installation) which +interactively scaffolds a Sanity Studio package. + # Commands See `moker --help` for a list of available commands. diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts index b54ebc5f..af579511 100644 --- a/packages/cli/src/commands/add.ts +++ b/packages/cli/src/commands/add.ts @@ -1,6 +1,6 @@ import { addWorkspace, - applyTemplate, + applyTemplateTask, formatTask, installPluginTask, isMonorepo, @@ -55,12 +55,10 @@ export const add = command("add") } for (const name of template) { - await task(`Apply template ${name}`, () => - applyTemplate({ - directory: workspaceDirectory, - name: name as string, - }), - ); + await applyTemplateTask({ + directory: workspaceDirectory, + name: name as string, + }); } for (const name of plugin) { diff --git a/packages/core/src/io.ts b/packages/core/src/io.ts index 026f0785..8110ab5e 100644 --- a/packages/core/src/io.ts +++ b/packages/core/src/io.ts @@ -1,5 +1,5 @@ import chalk from "chalk"; -import ora from "ora"; +import ora, { type Ora } from "ora"; type Log = | { @@ -88,12 +88,15 @@ export async function flushLogs() { resetMessages(); } -export async function task(title: string, callback: () => Promise) { +export async function task( + title: string, + callback: (spinner: Ora) => Promise, +) { const spinner = ora(title).start(); let result: T; try { - result = await callback(); + result = await callback(spinner); } catch (error: any) { spinner.fail(); logError(error); diff --git a/packages/core/src/template.ts b/packages/core/src/template.ts index f7403185..b1dafb33 100644 --- a/packages/core/src/template.ts +++ b/packages/core/src/template.ts @@ -5,21 +5,26 @@ export type TemplateArgs = { directory: string }; export type Template = { type: PluginType; + interactive?: boolean; apply: (args: TemplateArgs) => Promise; }; type TemplateOptions = { directory: string; name: string; + beforeApply?: (template: Template) => any | Promise; + afterApply?: (template: Template) => any | Promise; }; const CORE_TEMPLATES = [ + "bandersnatch", "common", - "lib", "cra", - "bandersnatch", "express", "github-action", + "lib", + "next", + "sanity", ]; export function isTemplate(template: unknown): template is Template { @@ -53,8 +58,21 @@ export async function importTemplate({ directory, name }: TemplateOptions) { return template; } -export async function applyTemplate({ directory, name }: TemplateOptions) { +export async function applyTemplate({ + directory, + name, + beforeApply, + afterApply, +}: TemplateOptions) { const template = await importTemplate({ directory, name }); + if (beforeApply) { + await beforeApply(template); + } + await template.apply({ directory }); + + if (afterApply) { + await afterApply(template); + } } diff --git a/packages/core/src/utils/tasks.ts b/packages/core/src/utils/tasks.ts index d1b89da4..f2349e86 100644 --- a/packages/core/src/utils/tasks.ts +++ b/packages/core/src/utils/tasks.ts @@ -4,6 +4,7 @@ import { hasPlugin, loadAllPlugins, } from "../plugin.js"; +import { applyTemplate } from "../template.js"; import { runDependencyQueues } from "../yarn.js"; import { exec } from "./exec.js"; @@ -19,6 +20,31 @@ export async function formatTask({ directory }: DirOption) { } } +export async function applyTemplateTask({ + directory, + name, +}: DirOption & { name: string }) { + await task(`Apply template ${name}`, (spinner) => + applyTemplate({ + directory, + name, + beforeApply: (template) => { + if (template.interactive) { + spinner.stopAndPersist({ + suffixText: "(interactive mode)", + symbol: "…", + }); + } + }, + afterApply: (template) => { + if (template.interactive) { + spinner.start(); + } + }, + }), + ); +} + export async function installPluginTask({ directory, name, diff --git a/packages/templates/src/index.ts b/packages/templates/src/index.ts index 30ecb8ff..7a72d235 100644 --- a/packages/templates/src/index.ts +++ b/packages/templates/src/index.ts @@ -4,3 +4,5 @@ export * from "./cra.js"; export * from "./express.js"; export * from "./githubAction.js"; export * from "./lib.js"; +export * from "./next.js"; +export * from "./sanity.js"; diff --git a/packages/templates/src/next.ts b/packages/templates/src/next.ts new file mode 100644 index 00000000..c9fd0459 --- /dev/null +++ b/packages/templates/src/next.ts @@ -0,0 +1,41 @@ +import { + PluginType, + exec, + readPackage, + removeDirectory, + writePackage, + type TemplateArgs, +} from "@mokr/core"; +import { basename, dirname } from "node:path"; + +async function apply({ directory }: TemplateArgs) { + const oldPackage = await readPackage({ directory }); + + await removeDirectory({ directory }); + + await exec( + "yarn", + [ + "dlx", + "create-next-app", + basename(directory), + "--app", + "--typescript", + "--tailwind", + "--eslint", + "--src-dir", + '--import-alias "@/*"', + "--use-yarn", + ], + { + cwd: dirname(directory), + }, + ); + + await writePackage({ directory, data: oldPackage }); +} + +export const next = { + type: PluginType.RepoOrWorkspace, + apply, +}; diff --git a/packages/templates/src/sanity.ts b/packages/templates/src/sanity.ts new file mode 100644 index 00000000..82050dd7 --- /dev/null +++ b/packages/templates/src/sanity.ts @@ -0,0 +1,47 @@ +import { + PluginType, + exec, + getMonorepoDirectory, + readPackage, + removeDirectory, + writePackage, + type TemplateArgs, +} from "@mokr/core"; +import { basename, dirname } from "node:path"; + +async function apply({ directory }: TemplateArgs) { + const monorepoDirectory = await getMonorepoDirectory({ directory }); + const oldPackage = await readPackage({ directory }); + + await removeDirectory({ directory }); + + await exec( + "yarn", + [ + "dlx", + "create-sanity", + "--output-path", + directory, + "--create-project", + monorepoDirectory + ? `${basename(monorepoDirectory)}-project` // Workaround for duplicate workspace name + : basename(directory), + "--dataset-default", + "--typescript", + "--template", + "clean", + ], + { + cwd: dirname(directory), + io: "passthrough", + }, + ); + + await writePackage({ directory, data: oldPackage }); +} + +export const sanity = { + type: PluginType.RepoOrWorkspace, + interactive: true, + apply, +};