diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index e6c324c9..0f2ee741 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -16,4 +16,4 @@ jobs: node-version: lts/* cache: yarn - run: yarn install - - run: yarn format-check + - run: yarn format:check diff --git a/README.md b/README.md index 7128e1b6..e7ba0b57 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # moker [![npm](https://img.shields.io/npm/v/moker)](https://www.npmjs.com/package/moker) -**No more struggles setting up monorepos. Kick-start monorepos, workspaces and -tooling:** +**No more struggles setting up new JavaScript repository. Kick-start +single-purpose repos, monorepos, monorepo workspaces and common tooling:** ```bash # initialize a monorepo -yarn dlx moker create my-monorepo -cd my-monorepo +yarn dlx moker create --monorepo my-repo +cd my-repo # install common tools yarn moker use prettier husky lint-staged doctoc semantic-release @@ -18,10 +18,10 @@ yarn moker add --template cra client ## Features -- 👢 Kick-start a monorepo with ease -- 🧰 Monorepo plugins to use pre-configured common tooling -- ➕ Add workspaces on demand -- 🧬 Workspace templates for a library, React app, API or CLI +- 👢 Kick-start a new repo or monorepo using Yarn +- 🧰 Plugins to use pre-configured common tooling +- ➕ Quickly add workspaces to a monorepo +- 🧬 Workspace templates for a shared library, React app, API or CLI - ⚡ Extensible, bring your own plugins > 🤓 The core plugins make some assumptions you may not agree with. If that's @@ -69,7 +69,7 @@ yarn moker add --template cra client ## Prerequisites -You will need Node v14+ and Yarn v3+ in order to use `moker`. +You will need Node v14+ and Yarn v2+ in order to use `moker`. - Install Node with [nvm](https://github.com/nvm-sh/nvm#install--update-script) or using [nodesource](https://github.com/nodesource/distributions#debinstall). @@ -84,7 +84,7 @@ You will need Node v14+ and Yarn v3+ in order to use `moker`. Create a new monorepo: ```bash -yarn dlx moker create my-repo +yarn dlx moker create --monorepo my-repo ``` This will initialize a new monorepo in the `my-repo` directory. @@ -197,7 +197,7 @@ level. ## `jest` _workspace_ -This plugin sets up [Jest](https://jestjs.io) and adds a `test` and `watch:test` +This plugin sets up [Jest](https://jestjs.io) and adds a `test` and `test:watch` script to both the workspace and the monorepo. ## `lint-staged` _monorepo_ @@ -238,7 +238,7 @@ _Current plan:_ - We can remove .npmrc file - We need to modify .yarnrc.yml / .releaserc.json - We can get rid of `"publishConfig"` in workspaces pkg -- We need to change `prepublishOnly` to `prepublish` +- [x] We need to change `prepublishOnly` to `prepublish` - Document weird command (esp. JSON string echo) - npm whoami fix not needed! @@ -288,7 +288,7 @@ If you have the `husky` plugin installed, it will also add a pre-commit hook. ## `typescript` _workspace_ This plugin sets up [TypeScript](https://www.typescriptlang.org) and adds a -`build` and `watch:build` script to both the workspace and the monorepo. +`build` and `build:watch` script to both the workspace and the monorepo. # Available templates @@ -324,10 +324,10 @@ Contributions are very welcome! ## Roadmap +- [ ] Adapt for non-monorepo use-cases (WIP) - [ ] Add LICENSE file to monorepo - [ ] Support for `swc`/`esbuild` - [ ] A compat lib (which builds cjs and mjs targets) -- [ ] Adapt for non-monorepo use-cases (?) - [ ] Blog post / tutorial - [ ] Docs for writing custom plugins / templates - [x] github-actions plugin diff --git a/TODO.md b/TODO.md index dbb9aa26..de101bbc 100644 --- a/TODO.md +++ b/TODO.md @@ -4,4 +4,4 @@ | :------------------------------------------------------------------------- | :----: | :------------------------------------------------------------ | | [packages/core/src/yarnrc.ts](packages/core/src/yarnrc.ts#L23) | 23 | etc, fix later | | [packages/cli/src/commands/list.ts](packages/cli/src/commands/list.ts#L15) | 15 | list workspaces using https://yarnpkg.com/cli/workspaces/list | -| [packages/plugins/src/jest/jest.ts](packages/plugins/src/jest/jest.ts#L35) | 35 | install jest without ts-jest | +| [packages/plugins/src/jest/jest.ts](packages/plugins/src/jest/jest.ts#L31) | 31 | install jest without ts-jest | diff --git a/package.json b/package.json index 69e98759..d7ba49fe 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,12 @@ "scripts": { "start": "node packages/cli/moker.js", "build": "yarn workspaces foreach --topological --verbose run build", + "build:watch": "yarn workspaces foreach --parallel --interlaced run build:watch", "test": "yarn workspaces foreach --topological --verbose run test", - "watch:build": "yarn workspaces foreach --parallel --interlaced run watch:build", - "watch:test": "yarn workspaces foreach --parallel --interlaced run watch:test", + "test:watch": "yarn workspaces foreach --parallel --interlaced run test:watch", "postinstall": "husky install && node scripts/postinstall.js", "format": "prettier --write --ignore-unknown .", - "format-check": "prettier --check --ignore-unknown .", + "format:check": "prettier --check --ignore-unknown .", "doctoc": "doctoc README.md", "todos": "leasot --exit-nicely --reporter markdown --ignore \"**/node_modules\" \"**/*.ts\" > TODO.md", "release": "semantic-release" diff --git a/packages/cli/package.json b/packages/cli/package.json index 15dca9d5..e595c064 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -15,12 +15,12 @@ ], "scripts": { "start": "node moker.js", - "prepublishOnly": "yarn clean && yarn build && cp ../../README.md .", + "prepublish": "yarn clean && yarn build && cp ../../README.md .", "clean": "rm -rf dist && rm -rf types", "build": "yarn clean && tsc", + "build:watch": "yarn build && tsc --watch", "test": "jest", - "watch:build": "tsc --watch", - "watch:test": "jest --watch" + "test:watch": "jest --watch" }, "dependencies": { "@mokr/core": "workspace:*", diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index 54c5d4b8..d9bd7c28 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -1,10 +1,12 @@ import { applyTemplate, createMonorepo, + createRepo, DEFAULT_LICENSE, DEFAULT_SCOPED, DEFAULT_WORKSPACES_DIRECTORY, installPlugin, + isReadableAndWritableDirectory, loadAllPlugins, runDependencyQueues, task, @@ -13,13 +15,17 @@ import { command } from "bandersnatch"; import { resolve } from "node:path"; export const create = command("create") - .description("Create a new monorepo") + .description("Create a new repo") .argument("path", { - description: "Monorepo path, basename will be used as the monorepo name.", - prompt: "What is the name of your monorepo?", + description: "Repo path, basename will be used as the name.", + prompt: "What is the name of your repo?", + }) + .option("monorepo", { + description: "Create a monorepo instead of a single-purpose repo", + type: "boolean", }) .option("template", { - description: "Use monorepo template", + description: "Use repo template", type: "string", }) .option("plugin", { @@ -27,12 +33,6 @@ export const create = command("create") type: "array", default: [] as string[], }) - .option("scoped", { - description: "Use scoped packages", - boolean: true, - prompt: "Do you want to use scoped package names?", - default: DEFAULT_SCOPED, - }) .option("license", { description: "License", choices: ["MIT", "GPLv3"], @@ -40,15 +40,30 @@ export const create = command("create") default: DEFAULT_LICENSE, }) .option("workspacesDirectory", { - description: "Workspaces directory", - prompt: "Which directory should we save workspaces to?", + description: "Workspaces directory (only used with --monorepo)", default: DEFAULT_WORKSPACES_DIRECTORY, }) - .action(async ({ path, template, plugin, ...options }) => { + .option("scoped", { + description: "Use scoped packages (only used with --monorepo)", + boolean: true, + default: DEFAULT_SCOPED, + }) + .option("force", { + description: + "Initialize repo even if path already exists. WARNING: some files may be overwritten!", + type: "boolean", + }) + .action(async ({ path, monorepo, template, plugin, force, ...options }) => { const directory = resolve(path); + const type = monorepo ? "monorepo" : "repo"; + const initializer = monorepo ? createMonorepo : createRepo; + + if ((await isReadableAndWritableDirectory({ directory })) && !force) { + throw new Error(`${directory} already exists`); + } - await task(`Create new monorepo in ${directory}`, () => - createMonorepo({ directory, ...options }) + await task(`Create new ${type} in ${directory}`, () => + initializer({ directory, ...options }) ); if (template) { diff --git a/packages/core/package.json b/packages/core/package.json index 354baa3c..3899cd03 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -16,12 +16,12 @@ "types" ], "scripts": { - "prepublishOnly": "yarn build", + "prepublish": "yarn build", "clean": "rm -rf dist && rm -rf types", "build": "yarn clean && tsc", + "build:watch": "yarn build && tsc --watch", "test": "jest", - "watch:build": "tsc --watch", - "watch:test": "jest --watch" + "test:watch": "jest --watch" }, "dependencies": { "chalk": "5.1.2", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6b3d8c66..4eca0716 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,6 +6,7 @@ export * from "./json.js"; export * from "./monorepo.js"; export * from "./package.js"; export * from "./plugin.js"; +export * from "./repo.js"; export * from "./template.js"; export * from "./utils/index.js"; export * from "./workspace.js"; diff --git a/packages/core/src/monorepo.ts b/packages/core/src/monorepo.ts index adc83321..5abaf949 100644 --- a/packages/core/src/monorepo.ts +++ b/packages/core/src/monorepo.ts @@ -1,10 +1,14 @@ import { join } from "node:path"; -import { isReadableAndWritableDirectory } from "./directory.js"; -import { hasPackage, Package, readPackage, writePackage } from "./package.js"; -import { enqueueInstallDependency, initYarn } from "./yarn.js"; +import { Package, readPackage } from "./package.js"; +import { + createRepo, + CreateRepoOptions, + DEFAULT_LICENSE, + isRepo, +} from "./repo.js"; +import { addYarnPlugin } from "./yarn.js"; export const DEFAULT_SCOPED = true; -export const DEFAULT_LICENSE = "MIT"; export const DEFAULT_WORKSPACES_DIRECTORY = "packages"; export type MonorepoPackage = Package & { @@ -19,12 +23,11 @@ type PkgOption = { pkg: Package; }; -type CreateMonorepoOptions = DirOption & { - scoped?: boolean; - license?: string; - initialVersion?: string; - workspacesDirectory?: string; -}; +export type CreateMonorepoOptions = DirOption & + CreateRepoOptions & { + scoped?: boolean; + workspacesDirectory?: string; + }; export async function createMonorepo({ directory, @@ -32,37 +35,23 @@ export async function createMonorepo({ license = DEFAULT_LICENSE, workspacesDirectory = DEFAULT_WORKSPACES_DIRECTORY, }: CreateMonorepoOptions) { - if (await isReadableAndWritableDirectory({ directory })) { - throw new Error(`${directory} already exists`); - } - - await initYarn({ directory }); - - await writePackage({ + await createRepo({ directory, - data: { - license, + license, + additionalPackageOptions: { + private: true, workspaces: [`${workspacesDirectory}/*`], moker: { scoped, - plugins: [], - }, - scripts: { - build: "echo 'not implemented'", - test: "echo 'not implemented'", }, }, }); - enqueueInstallDependency({ - directory, - identifier: "moker", - dev: true, - }); + await addYarnPlugin({ directory, name: "workspace-tools" }); } export async function isMonorepo({ directory }: DirOption) { - if (!(await hasPackage({ directory }))) { + if (!(await isRepo({ directory }))) { return false; } diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts index ea8f6e6a..8679b881 100644 --- a/packages/core/src/plugin.ts +++ b/packages/core/src/plugin.ts @@ -1,24 +1,30 @@ import { isMonorepo } from "./monorepo.js"; import { readPackage, updatePackage, writePackage } from "./package.js"; +import { isRepo } from "./repo.js"; import { toCamelCase } from "./utils/string.js"; +import { isWorkspace } from "./workspace.js"; export type PluginArgs = { directory: string }; export enum PluginType { + Repo = "repo", + RepoOrWorkspace = "repoOrWorkspace", Monorepo = "monorepo", Workspace = "workspace", - Any = "any", } export type Plugin = { - type: string | PluginType; + type: PluginType; install: (args: PluginArgs) => Promise; remove: (args: PluginArgs) => Promise; load: (args: PluginArgs) => Promise; }; -type PluginOptions = { +type DirOption = { directory: string; +}; + +type PluginOptions = DirOption & { name: string; }; @@ -66,20 +72,46 @@ export async function importPlugin({ directory, name }: PluginOptions) { throw new Error(`Plugin ${name} does not exist or is not valid`); } - // Monorepo level? - if (await isMonorepo({ directory })) { - if (plugin.type === "workspace") { - throw new Error(`Plugin ${name} can only be used at workspace level`); - } - } else { - if (plugin.type === "monorepo") { - throw new Error(`Plugin ${name} can only be used at monorepo level`); - } - } + await validateType({ directory, type: plugin.type }); return plugin; } +export async function validateType({ + directory, + type, +}: DirOption & { type: PluginType }) { + const repo = await isRepo({ directory }); + const monorepo = await isMonorepo({ directory }); + const workspace = await isWorkspace({ directory }); + + switch (type) { + case PluginType.Repo: + if (!repo) { + throw new Error(`Plugin can only be used at repo level`); + } + break; + + case PluginType.RepoOrWorkspace: + if (!repo && !workspace) { + throw new Error(`Plugin can only be used at repo or workspace level`); + } + break; + + case PluginType.Monorepo: + if (!monorepo) { + throw new Error(`Plugin can only be used at monorepo level`); + } + break; + + case PluginType.Workspace: + if (!workspace) { + throw new Error(`Plugin can only be used at workspace level`); + } + break; + } +} + export async function installPlugin({ directory, name }: PluginOptions) { if (await hasPlugin({ directory, name })) { throw new Error(`Plugin ${name} is already installed`); diff --git a/packages/core/src/repo.ts b/packages/core/src/repo.ts new file mode 100644 index 00000000..4c4d870b --- /dev/null +++ b/packages/core/src/repo.ts @@ -0,0 +1,67 @@ +import { join } from "node:path"; +import { isDirectory, isReadableAndWritableDirectory } from "./directory.js"; +import { hasPackage, Package, writePackage } from "./package.js"; +import { writeReadme } from "./workspace.js"; +import { + enqueueInstallDependency, + initYarnExistingRepo, + initYarnNewRepo, +} from "./yarn.js"; + +export const DEFAULT_LICENSE = "MIT"; + +export type RepoPackage = Package; + +type DirOption = { + directory: string; +}; + +export type CreateRepoOptions = DirOption & { + license?: string; + additionalPackageOptions?: T; +}; + +export async function createRepo({ + directory, + license = DEFAULT_LICENSE, + additionalPackageOptions, +}: CreateRepoOptions) { + if (await isReadableAndWritableDirectory({ directory })) { + await initYarnExistingRepo({ directory }); + } else { + await initYarnNewRepo({ directory }); + } + + await writePackage({ + directory, + data: { + license, + moker: { + plugins: [], + }, + scripts: { + build: "echo 'not implemented'", + test: "echo 'not implemented'", + }, + }, + }); + + if (additionalPackageOptions) { + await writePackage({ directory, data: additionalPackageOptions }); + } + + enqueueInstallDependency({ + directory, + identifier: "moker", + dev: true, + }); + + await writeReadme({ directory }); +} + +export async function isRepo({ directory }: DirOption) { + return ( + (await isDirectory({ directory: join(directory, ".git") })) && + (await hasPackage({ directory })) + ); +} diff --git a/packages/core/src/template.ts b/packages/core/src/template.ts index 8a1db47e..c09aaa4d 100644 --- a/packages/core/src/template.ts +++ b/packages/core/src/template.ts @@ -1,16 +1,10 @@ -import { isMonorepo } from "./monorepo.js"; +import { PluginType, validateType } from "./plugin.js"; import { toCamelCase } from "./utils/string.js"; export type TemplateArgs = { directory: string }; -export enum TemplateType { - Monorepo = "monorepo", - Workspace = "workspace", - Any = "any", -} - export type Template = { - type: string | TemplateType; + type: PluginType; apply: (args: TemplateArgs) => Promise; }; @@ -47,16 +41,7 @@ export async function importTemplate({ directory, name }: TemplateOptions) { throw new Error(`Template ${name} does not exist or is not valid`); } - // Monorepo level? - if (await isMonorepo({ directory })) { - if (template.type === "workspace") { - throw new Error(`Template ${name} can only be used at workspace level`); - } - } else { - if (template.type === "monorepo") { - throw new Error(`Template ${name} can only be used at monorepo level`); - } - } + await validateType({ directory, type: template.type }); return template; } diff --git a/packages/core/src/utils/string.ts b/packages/core/src/utils/string.ts index d051f432..e1528789 100644 --- a/packages/core/src/utils/string.ts +++ b/packages/core/src/utils/string.ts @@ -3,3 +3,10 @@ export function toCamelCase(str: string) { .toLowerCase() .replace(/[-_][a-z]/g, (group) => group.slice(-1).toUpperCase()); } + +export function toSnakeCase(str: string) { + return str + .replace(/([a-z])([A-Z])/g, "$1_$2") + .replace(/[\s-]+/g, "_") + .toLowerCase(); +} diff --git a/packages/core/src/workspace.ts b/packages/core/src/workspace.ts index 82051f76..5a892f75 100644 --- a/packages/core/src/workspace.ts +++ b/packages/core/src/workspace.ts @@ -1,9 +1,9 @@ -import { dirname, join } from "node:path"; +import { basename, dirname, join } from "node:path"; import { pkgUp } from "pkg-up"; import { isReadableAndWritableDirectory } from "./directory.js"; import { writeFile } from "./file.js"; -import { getScoped, getWorkspacesDirectory } from "./monorepo.js"; -import { readPackage, writePackage } from "./package.js"; +import { getScoped, getWorkspacesDirectory, isMonorepo } from "./monorepo.js"; +import { hasPackage, readPackage, writePackage } from "./package.js"; type DirOption = { directory: string; @@ -52,20 +52,43 @@ export async function addWorkspace({ }, }); + await writeReadme({ directory, name: packageName }); + + return workspaceDirectory; +} + +export async function writeReadme({ + directory, + name, +}: DirOption & { name?: string }) { await writeFile({ - path: join(workspaceDirectory, "README.md"), - contents: `# ${packageName}`, + path: join(directory, "README.md"), + contents: `# ${name ?? basename(directory)}`, }); +} - return workspaceDirectory; +export async function isWorkspace({ directory }: DirOption) { + if (!(await hasPackage({ directory }))) { + return false; + } + + if (await getMonorepoDirectory({ directory })) { + return true; + } + + return false; } export async function getMonorepoDirectory({ directory }: DirOption) { const path = await pkgUp({ cwd: dirname(directory) }); - if (path) { - return dirname(path); + if (!path) { + return; } - return; + const parentPackageDirectory = dirname(path); + + return (await isMonorepo({ directory: parentPackageDirectory })) + ? parentPackageDirectory + : undefined; } diff --git a/packages/core/src/yarn.ts b/packages/core/src/yarn.ts index 6bb2e4ba..62d31193 100644 --- a/packages/core/src/yarn.ts +++ b/packages/core/src/yarn.ts @@ -8,6 +8,10 @@ type DirOption = { }; const GITIGNORE_LINES = [ + "# node", + "node_modules", + "", + "# yarn", ".pnp.*", ".yarn/*", "!.yarn/patches", @@ -23,18 +27,26 @@ const queues = { remove: new Map>(), }; -export async function initYarn({ directory }: DirOption) { +export async function initYarnExistingRepo({ directory }: DirOption) { + await exec("yarn", ["set", "version", "latest"], { cwd: directory }); + + await initYarn({ directory }); +} + +export async function initYarnNewRepo({ directory }: DirOption) { await createDirectory({ directory }); await exec("yarn", ["init", "-2"], { cwd: directory }); + await initYarn({ directory }); +} + +async function initYarn({ directory }: DirOption) { await writeYarnrc({ directory, data: { nodeLinker: "node-modules" } }); await writeGitignore({ directory, lines: GITIGNORE_LINES, append: false }); await addYarnPlugin({ directory, name: "interactive-tools" }); - - await addYarnPlugin({ directory, name: "workspace-tools" }); } export async function addYarnPlugin({ diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 78ba0b40..264d3fbe 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -17,12 +17,12 @@ "static" ], "scripts": { - "prepublishOnly": "yarn build", + "prepublish": "yarn build", "clean": "rm -rf dist && rm -rf types", "build": "yarn clean && tsc", - "watch:build": "tsc --watch", + "build:watch": "yarn build && tsc --watch", "test": "jest", - "watch:test": "jest --watch" + "test:watch": "jest --watch" }, "dependencies": { "@mokr/core": "workspace:*" diff --git a/packages/plugins/src/dependabot/dependabot.ts b/packages/plugins/src/dependabot/dependabot.ts index b9e8a3e2..95372030 100644 --- a/packages/plugins/src/dependabot/dependabot.ts +++ b/packages/plugins/src/dependabot/dependabot.ts @@ -169,7 +169,7 @@ async function load({ directory }: PluginArgs) { } export const dependabot = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/devcontainer/devcontainer.ts b/packages/plugins/src/devcontainer/devcontainer.ts index ad947fa5..278b1baa 100644 --- a/packages/plugins/src/devcontainer/devcontainer.ts +++ b/packages/plugins/src/devcontainer/devcontainer.ts @@ -44,7 +44,7 @@ async function load({ directory }: PluginArgs) { } export const devcontainer = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/doctoc/doctoc.ts b/packages/plugins/src/doctoc/doctoc.ts index ef7e1ed4..0641dd40 100644 --- a/packages/plugins/src/doctoc/doctoc.ts +++ b/packages/plugins/src/doctoc/doctoc.ts @@ -65,7 +65,7 @@ async function load({ directory }: PluginArgs) { } export const doctoc = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/githubActions/githubActions.ts b/packages/plugins/src/githubActions/githubActions.ts index 7efafd77..e4333a23 100644 --- a/packages/plugins/src/githubActions/githubActions.ts +++ b/packages/plugins/src/githubActions/githubActions.ts @@ -39,7 +39,7 @@ async function load({ directory }: PluginArgs) { // Monorepo plugins - if (await hasPlugin({ directory, name: "semanticRelease" })) { + if (await hasPlugin({ directory, name: "semantic-release" })) { await copyFile({ from: new URL("../../static/release.yml", import.meta.url).pathname, to: join(workflowDirectory, "release.yml"), @@ -55,7 +55,7 @@ async function load({ directory }: PluginArgs) { } export const githubActions = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/husky/husky.ts b/packages/plugins/src/husky/husky.ts index 8452e082..f865bf83 100644 --- a/packages/plugins/src/husky/husky.ts +++ b/packages/plugins/src/husky/husky.ts @@ -61,7 +61,7 @@ async function remove({ directory }: PluginArgs) { async function load() {} export const husky = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/jest/jest.ts b/packages/plugins/src/jest/jest.ts index 2753d8d6..b02b2349 100644 --- a/packages/plugins/src/jest/jest.ts +++ b/packages/plugins/src/jest/jest.ts @@ -27,16 +27,10 @@ export default { async function install({ directory }: PluginArgs) { const monorepoDirectory = await getMonorepoDirectory({ directory }); - if (!monorepoDirectory) { - throw new Error("Could not find monorepo directory"); - } - if (!hasPlugin({ directory, name: "typescript" })) { // @todo: install jest without ts-jest - // } else { // Install jest with ts-jest - enqueueInstallDependency({ directory, identifier: ["jest", "ts-jest", "@types/jest"], @@ -54,45 +48,40 @@ async function install({ directory }: PluginArgs) { data: { scripts: { test: "jest", - "watch:test": "jest --watch", + "test:watch": "jest --watch", }, }, }); - // At monorepo level - - await writePackage({ - directory: monorepoDirectory, - data: { - scripts: { - test: "yarn workspaces foreach --topological --verbose run test", - "watch:test": - "yarn workspaces foreach --parallel --interlaced run watch:test", + if (monorepoDirectory) { + // At monorepo level + await writePackage({ + directory: monorepoDirectory, + data: { + scripts: { + test: "yarn workspaces foreach --topological --verbose run test", + "test:watch": + "yarn workspaces foreach --parallel --interlaced run test:watch", + }, }, - }, - }); + }); + } } async function remove({ directory }: PluginArgs) { - const monorepoDirectory = await getMonorepoDirectory({ directory }); - - if (!monorepoDirectory) { - throw new Error("Could not find monorepo directory"); - } - enqueueRemoveDependency({ directory, identifier: ["jest", "ts-jest"] }); try { await removeFile({ path: join(directory, JEST_CONFIG_FILENAME) }); } catch {} - logWarning("Please review your workspace and root package.json manually"); + logWarning("Please review package.json manually"); } async function load() {} export const jest = { - type: PluginType.Workspace, + type: PluginType.RepoOrWorkspace, install, remove, load, diff --git a/packages/plugins/src/lintStaged/lintStaged.ts b/packages/plugins/src/lintStaged/lintStaged.ts index 3020699c..da00590b 100644 --- a/packages/plugins/src/lintStaged/lintStaged.ts +++ b/packages/plugins/src/lintStaged/lintStaged.ts @@ -59,7 +59,7 @@ async function load({ directory }: PluginArgs) { } export const lintStaged = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/prettier/prettier.ts b/packages/plugins/src/prettier/prettier.ts index c13aae40..645bbcec 100644 --- a/packages/plugins/src/prettier/prettier.ts +++ b/packages/plugins/src/prettier/prettier.ts @@ -41,7 +41,7 @@ async function install({ directory }: PluginArgs) { data: { scripts: { format: "prettier --write --ignore-unknown .", - "format-check": "prettier --check --ignore-unknown .", + "format:check": "prettier --check --ignore-unknown .", }, }, }); @@ -75,7 +75,7 @@ async function remove({ directory }: PluginArgs) { async function load() {} export const prettier = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/semanticRelease/semanticRelease.ts b/packages/plugins/src/semanticRelease/semanticRelease.ts index 2dffd0f6..96fd3598 100644 --- a/packages/plugins/src/semanticRelease/semanticRelease.ts +++ b/packages/plugins/src/semanticRelease/semanticRelease.ts @@ -94,7 +94,7 @@ async function remove({ directory }: PluginArgs) { async function load() {} export const semanticRelease = { - type: PluginType.Monorepo, + type: PluginType.RepoOrWorkspace, install, remove, load, diff --git a/packages/plugins/src/todos/todos.ts b/packages/plugins/src/todos/todos.ts index 5740d0ff..ff13b428 100644 --- a/packages/plugins/src/todos/todos.ts +++ b/packages/plugins/src/todos/todos.ts @@ -66,7 +66,7 @@ async function load({ directory }: PluginArgs) { } export const todos = { - type: PluginType.Monorepo, + type: PluginType.Repo, install, remove, load, diff --git a/packages/plugins/src/typescript/typescript.ts b/packages/plugins/src/typescript/typescript.ts index 508434d0..7904578b 100644 --- a/packages/plugins/src/typescript/typescript.ts +++ b/packages/plugins/src/typescript/typescript.ts @@ -10,15 +10,48 @@ import { writeGitignore, writePackage, } from "@mokr/core"; -import { removeTsconfig, writeTsconfig } from "./tsconfig.js"; +import deepmerge from "deepmerge"; +import { removeTsconfig, Tsconfig, writeTsconfig } from "./tsconfig.js"; + +const TSCONFIG_WORKSPACE: Tsconfig = { + compilerOptions: { + rootDir: "src", + outDir: "dist", + declarationDir: "types", + }, + include: ["src/**/*"], +}; +const TSCONFIG_BASE: Tsconfig = { + $schema: "https://json.schemastore.org/tsconfig", + display: "Node 16 + ESM + Strictest", + compilerOptions: { + lib: ["es2021"], + module: "es2022", + target: "es2021", + moduleResolution: "node", + strict: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + allowUnusedLabels: false, + allowUnreachableCode: false, + exactOptionalPropertyTypes: true, + noFallthroughCasesInSwitch: true, + noImplicitOverride: true, + noImplicitReturns: true, + noPropertyAccessFromIndexSignature: true, + noUncheckedIndexedAccess: true, + noUnusedLocals: true, + noUnusedParameters: true, + importsNotUsedAsValues: "error", + declaration: true, + declarationMap: true, + }, +}; async function install({ directory }: PluginArgs) { const monorepoDirectory = await getMonorepoDirectory({ directory }); - if (!monorepoDirectory) { - throw new Error("Could not find monorepo directory"); - } - enqueueInstallDependency({ directory, identifier: ["typescript", "@types/node"], @@ -27,19 +60,6 @@ async function install({ directory }: PluginArgs) { await addYarnPlugin({ directory, name: "typescript" }); - await writeTsconfig({ - directory, - data: { - extends: "../../tsconfig.json", - compilerOptions: { - rootDir: "src", - outDir: "dist", - declarationDir: "types", - }, - include: ["src/**/*"], - }, - }); - await writeGitignore({ directory, lines: ["# artifacts", "/dist", "/types"], @@ -53,66 +73,53 @@ async function install({ directory }: PluginArgs) { types: "types/index.d.ts", files: ["dist", "types"], scripts: { - prepublishOnly: "yarn build", + prepublish: "yarn build", clean: "rm -rf dist && rm -rf types", build: "yarn clean && tsc", - "watch:build": "tsc --watch", + "build:watch": "yarn build && tsc --watch", }, }, }); - // At monorepo level - - await writeTsconfig({ - directory: monorepoDirectory, - data: { - $schema: "https://json.schemastore.org/tsconfig", - display: "Node 16 + ESM + Strictest", - compilerOptions: { - lib: ["es2021"], - module: "es2022", - target: "es2021", - moduleResolution: "node", - strict: true, - esModuleInterop: true, - skipLibCheck: true, - forceConsistentCasingInFileNames: true, - allowUnusedLabels: false, - allowUnreachableCode: false, - exactOptionalPropertyTypes: true, - noFallthroughCasesInSwitch: true, - noImplicitOverride: true, - noImplicitReturns: true, - noPropertyAccessFromIndexSignature: true, - noUncheckedIndexedAccess: true, - noUnusedLocals: true, - noUnusedParameters: true, - importsNotUsedAsValues: "error", - declaration: true, - declarationMap: true, + if (!monorepoDirectory) { + // Single tsconfig + await writeTsconfig({ + directory, + data: deepmerge(TSCONFIG_BASE, TSCONFIG_WORKSPACE), + }); + } else { + // Workspace tsconfig + await writeTsconfig({ + directory, + data: { + extends: "../../tsconfig.json", + ...TSCONFIG_WORKSPACE, }, - }, - }); - - await writePackage({ - directory: monorepoDirectory, - data: { - scripts: { - build: "yarn workspaces foreach --topological --verbose run build", - "watch:build": - "yarn workspaces foreach --parallel --interlaced run watch:build", + }); + + // Monorepo tsconfig + await writeTsconfig({ + directory: monorepoDirectory, + data: TSCONFIG_BASE, + }); + + // Monorepo scripts + await writePackage({ + directory: monorepoDirectory, + data: { + scripts: { + build: "yarn workspaces foreach --topological --verbose run build", + "build:watch": + "yarn workspaces foreach --parallel --interlaced run build:watch", + }, }, - }, - }); + }); + } } async function remove({ directory }: PluginArgs) { const monorepoDirectory = await getMonorepoDirectory({ directory }); - if (!monorepoDirectory) { - throw new Error("Could not find monorepo directory"); - } - enqueueRemoveDependency({ directory, identifier: ["typescript", "@types/node"], @@ -120,16 +127,19 @@ async function remove({ directory }: PluginArgs) { await removeYarnPlugin({ directory, name: "typescript" }); - await removeTsconfig({ directory: monorepoDirectory }); await removeTsconfig({ directory }); - logWarning("Please review your workspace and root package.json manually"); + if (monorepoDirectory) { + await removeTsconfig({ directory: monorepoDirectory }); + } + + logWarning("Please review your package.json manually"); } async function load() {} export const typescript = { - type: PluginType.Workspace, + type: PluginType.RepoOrWorkspace, install, remove, load, diff --git a/packages/plugins/static/format-check.yml b/packages/plugins/static/format-check.yml index e6c324c9..0f2ee741 100644 --- a/packages/plugins/static/format-check.yml +++ b/packages/plugins/static/format-check.yml @@ -16,4 +16,4 @@ jobs: node-version: lts/* cache: yarn - run: yarn install - - run: yarn format-check + - run: yarn format:check diff --git a/packages/templates/package.json b/packages/templates/package.json index bd243dcb..6cad84cc 100644 --- a/packages/templates/package.json +++ b/packages/templates/package.json @@ -16,12 +16,12 @@ "types" ], "scripts": { - "prepublishOnly": "yarn build", + "prepublish": "yarn build", "clean": "rm -rf dist && rm -rf types", "build": "yarn clean && tsc", - "watch:build": "tsc --watch", + "build:watch": "yarn build && tsc --watch", "test": "jest", - "watch:test": "jest --watch" + "test:watch": "jest --watch" }, "dependencies": { "@mokr/core": "workspace:*" diff --git a/packages/templates/src/bandersnatch.ts b/packages/templates/src/bandersnatch.ts index 1087b50c..f243fdd6 100644 --- a/packages/templates/src/bandersnatch.ts +++ b/packages/templates/src/bandersnatch.ts @@ -1,8 +1,8 @@ import { enqueueInstallDependency, installPlugin, + PluginType, TemplateArgs, - TemplateType, writeFile, writePackage, } from "@mokr/core"; @@ -53,6 +53,6 @@ cli.run().catch(console.error); } export const bandersnatch = { - type: TemplateType.Workspace, + type: PluginType.Workspace, apply, }; diff --git a/packages/templates/src/common.ts b/packages/templates/src/common.ts index 104c0752..0077d6fa 100644 --- a/packages/templates/src/common.ts +++ b/packages/templates/src/common.ts @@ -1,4 +1,4 @@ -import { installPlugin, TemplateArgs, TemplateType } from "@mokr/core"; +import { installPlugin, PluginType, TemplateArgs } from "@mokr/core"; async function apply({ directory }: TemplateArgs) { await installPlugin({ directory, name: "prettier" }); @@ -12,6 +12,6 @@ async function apply({ directory }: TemplateArgs) { } export const common = { - type: TemplateType.Monorepo, + type: PluginType.Monorepo, apply, }; diff --git a/packages/templates/src/cra.ts b/packages/templates/src/cra.ts index 39e83b16..b05b5961 100644 --- a/packages/templates/src/cra.ts +++ b/packages/templates/src/cra.ts @@ -1,22 +1,17 @@ import { exec, getMonorepoDirectory, + PluginType, readPackage, removeFile, removePackage, TemplateArgs, - TemplateType, writePackage, } from "@mokr/core"; import { basename, dirname, join } from "node:path"; async function apply({ directory }: TemplateArgs) { const monorepoDirectory = await getMonorepoDirectory({ directory }); - - if (!monorepoDirectory) { - throw new Error("Could not find monorepo directory"); - } - const oldPackage = await readPackage({ directory }); await removePackage({ directory }); @@ -38,12 +33,14 @@ async function apply({ directory }: TemplateArgs) { ); // Weird problem where we are left with a package-lock.json file after installation - await removeFile({ path: join(monorepoDirectory, "package-lock.json") }); + await removeFile({ + path: join(monorepoDirectory ?? directory, "package-lock.json"), + }); await writePackage({ directory, data: oldPackage }); } export const cra = { - type: TemplateType.Workspace, + type: PluginType.Workspace, apply, }; diff --git a/packages/templates/src/express.ts b/packages/templates/src/express.ts index cbceafbb..19881008 100644 --- a/packages/templates/src/express.ts +++ b/packages/templates/src/express.ts @@ -1,8 +1,8 @@ import { enqueueInstallDependency, installPlugin, + PluginType, TemplateArgs, - TemplateType, writeFile, writePackage, } from "@mokr/core"; @@ -54,6 +54,6 @@ app.listen(process.env.PORT || 3000); } export const express = { - type: TemplateType.Workspace, + type: PluginType.Workspace, apply, }; diff --git a/packages/templates/src/lib.ts b/packages/templates/src/lib.ts index 886be922..5330b9e0 100644 --- a/packages/templates/src/lib.ts +++ b/packages/templates/src/lib.ts @@ -1,9 +1,4 @@ -import { - installPlugin, - TemplateArgs, - TemplateType, - writeFile, -} from "@mokr/core"; +import { installPlugin, PluginType, TemplateArgs, writeFile } from "@mokr/core"; import { join } from "path"; async function apply({ directory }: TemplateArgs) { @@ -37,6 +32,6 @@ test("adds 1 + 2 to equal 3", () => { } export const lib = { - type: TemplateType.Workspace, + type: PluginType.Workspace, apply, };