diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 00000000..c8a7c79a --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,294 @@ +{ + "badgeTemplate": "\"All 🤝\" src=\"https://img.shields.io/badge/all_contributors-<%= contributors.length %>_🤝-21bb42.svg\" />", + "commit": false, + "commitConvention": "angular", + "contributors": [ + { + "avatar_url": "https://avatars.githubusercontent.com/u/3335181?v=4", + "contributions": [ + "bug", + "code", + "maintenance", + "review", + "tool", + "doc", + "infra", + "test" + ], + "login": "JoshuaKGoldberg", + "name": "Josh Goldberg", + "profile": "http://www.joshuakgoldberg.com" + }, + { + "login": "sinchang", + "name": "Jeff Wen", + "avatar_url": "https://avatars.githubusercontent.com/u/3297859?v=4", + "profile": "https://sinchang.me", + "contributions": [ + "code" + ] + }, + { + "login": "Pinjasaur", + "name": "Paul Esch-Laurent", + "avatar_url": "https://avatars.githubusercontent.com/u/6335792?v=4", + "profile": "https://paulisaweso.me/", + "contributions": [ + "code" + ] + }, + { + "login": "NazCodeland", + "name": "NazCodeland", + "avatar_url": "https://avatars.githubusercontent.com/u/113494366?v=4", + "profile": "https://github.com/NazCodeland", + "contributions": [ + "code" + ] + }, + { + "login": "johnnyreilly", + "name": "John Reilly", + "avatar_url": "https://avatars.githubusercontent.com/u/1010525?v=4", + "profile": "https://blog.johnnyreilly.com/", + "contributions": [ + "code" + ] + }, + { + "login": "webpro", + "name": "Lars Kappert", + "avatar_url": "https://avatars.githubusercontent.com/u/456426?v=4", + "profile": "https://webpro.nl", + "contributions": [ + "code" + ] + }, + { + "login": "RebeccaStevens", + "name": "Rebecca Stevens", + "avatar_url": "https://avatars.githubusercontent.com/u/7224206?v=4", + "profile": "https://github.com/RebeccaStevens", + "contributions": [ + "code", + "infra" + ] + }, + { + "login": "ronthetech", + "name": "Ron Jean-Francois", + "avatar_url": "https://avatars.githubusercontent.com/u/105710107?v=4", + "profile": "http://ronjeanfrancois.com", + "contributions": [ + "code", + "infra" + ] + }, + { + "login": "nowyDEV", + "name": "Dominik Nowik", + "avatar_url": "https://avatars.githubusercontent.com/u/12304307?v=4", + "profile": "https://github.com/nowyDEV", + "contributions": [ + "tool", + "code" + ] + }, + { + "login": "TAKANOME-DEV", + "name": "takanomedev", + "avatar_url": "https://avatars.githubusercontent.com/u/79809121?v=4", + "profile": "https://github.com/TAKANOME-DEV", + "contributions": [ + "code" + ] + }, + { + "login": "emday4prez", + "name": "Emerson", + "avatar_url": "https://avatars.githubusercontent.com/u/35363144?v=4", + "profile": "https://github.com/emday4prez", + "contributions": [ + "code" + ] + }, + { + "login": "jsjoeio", + "name": "Joe Previte", + "avatar_url": "https://avatars.githubusercontent.com/u/3806031?v=4", + "profile": "https://typescriptcourse.com/tutorials", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "navin-moorthy", + "name": "Navin Moorthy", + "avatar_url": "https://avatars.githubusercontent.com/u/39694575?v=4", + "profile": "https://navinmoorthy.me/", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "garuna-m6", + "name": "Anurag", + "avatar_url": "https://avatars.githubusercontent.com/u/23234342?v=4", + "profile": "https://github.com/garuna-m6", + "contributions": [ + "code" + ] + }, + { + "login": "danielroe", + "name": "Daniel Roe", + "avatar_url": "https://avatars.githubusercontent.com/u/28706372?v=4", + "profile": "https://roe.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "the-lazy-learner", + "name": "Sudhansu", + "avatar_url": "https://avatars.githubusercontent.com/u/13695177?v=4", + "profile": "https://github.com/the-lazy-learner", + "contributions": [ + "code" + ] + }, + { + "login": "RNR1", + "name": "Ron Braha", + "avatar_url": "https://avatars.githubusercontent.com/u/45559220?v=4", + "profile": "https://linktr.ee/ronbraha", + "contributions": [ + "code", + "design", + "test" + ] + }, + { + "login": "tungbq", + "name": "Tung Bui (Leo)", + "avatar_url": "https://avatars.githubusercontent.com/u/85242618?v=4", + "profile": "https://github.com/tungbq", + "contributions": [ + "code" + ] + }, + { + "login": "orta", + "name": "Orta Therox", + "avatar_url": "https://avatars.githubusercontent.com/u/49038?v=4", + "profile": "https://orta.io", + "contributions": [ + "code" + ] + }, + { + "login": "promise-dash", + "name": "Promise Dash", + "avatar_url": "https://avatars.githubusercontent.com/u/86062880?v=4", + "profile": "https://github.com/promise-dash", + "contributions": [ + "code" + ] + }, + { + "login": "jolg42", + "name": "Joël Galeran", + "avatar_url": "https://avatars.githubusercontent.com/u/1328733?v=4", + "profile": "https://twitter.com/Jolg42", + "contributions": [ + "code" + ] + }, + { + "login": "kristo-baricevic", + "name": "Kristo Baricevic", + "avatar_url": "https://avatars.githubusercontent.com/u/108290619?v=4", + "profile": "https://kristo-baricevic.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "ryota-murakami", + "name": "Ryota Murakami", + "avatar_url": "https://avatars.githubusercontent.com/u/5501268?v=4", + "profile": "https://ryota-murakami.github.io/", + "contributions": [ + "code", + "bug" + ] + }, + { + "login": "ruthwikreddy09", + "name": "Ruthwik", + "avatar_url": "https://avatars.githubusercontent.com/u/126862059?v=4", + "profile": "https://github.com/RuthwikReddy09", + "contributions": [ + "code" + ] + }, + { + "login": "jdwilkin4", + "name": "Jessica Wilkins ", + "avatar_url": "https://avatars.githubusercontent.com/u/67210629?v=4", + "profile": "https://jessicawilkins.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "vasanth9", + "name": "Vasanth Kumar Cheepurupalli", + "avatar_url": "https://avatars.githubusercontent.com/u/42891954?v=4", + "profile": "https://github.com/vasanth9", + "contributions": [ + "code" + ] + }, + { + "login": "conrmahr", + "name": "Conor Meagher", + "avatar_url": "https://avatars.githubusercontent.com/u/363781?v=4", + "profile": "https://conormeagher.com/", + "contributions": [ + "code" + ] + }, + { + "login": "DanexQ", + "name": "Daniel", + "avatar_url": "https://avatars.githubusercontent.com/u/72567464?v=4", + "profile": "https://github.com/DanexQ", + "contributions": [ + "infra" + ] + }, + { + "login": "jaas666", + "name": "Juan A.", + "avatar_url": "https://avatars.githubusercontent.com/u/30204147?v=4", + "profile": "https://github.com/jaas666", + "contributions": [ + "code" + ] + } + ], + "contributorsPerLine": 7, + "contributorsSortAlphabetically": true, + "files": [ + "README.md" + ], + "imageSize": 100, + "projectName": "create-typescript-app", + "projectOwner": "JoshuaKGoldberg", + "repoHost": "https://github.com", + "repoType": "github", + "commitType": "docs" +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..06a1beac --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +!.* +coverage* +lib +node_modules +pnpm-lock.yaml diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..86cb176e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,176 @@ +/* +👋 Hi! This ESLint configuration contains a lot more stuff than many repos'! +You can read from it to see all sorts of linting goodness, but don't worry - +it's not something you need to exhaustively understand immediately. 💙 + +If you're interested in learning more, see the 'getting started' docs on: +- ESLint: https://eslint.org +- typescript-eslint: https://typescript-eslint.io +*/ + +/** @type {import("@types/eslint").Linter.Config} */ +module.exports = { + env: { + es2022: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:eslint-comments/recommended", + "plugin:n/recommended", + "plugin:perfectionist/recommended-natural", + "plugin:regexp/recommended", + "plugin:vitest/recommended", + ], + overrides: [ + { + extends: ["plugin:markdown/recommended"], + files: ["**/*.md"], + processor: "markdown/markdown", + }, + { + extends: [ + "plugin:jsdoc/recommended-typescript-error", + "plugin:@typescript-eslint/strict", + "plugin:@typescript-eslint/stylistic", + ], + files: ["**/*.ts"], + parser: "@typescript-eslint/parser", + rules: { + // These off-by-default rules work well for this repo and we like them on. + "jsdoc/informative-docs": "error", + "logical-assignment-operators": [ + "error", + "always", + { enforceForIfStatements: true }, + ], + "operator-assignment": "error", + + // These on-by-default rules don't work well for this repo and we like them off. + "jsdoc/require-jsdoc": "off", + "jsdoc/require-param": "off", + "jsdoc/require-property": "off", + "jsdoc/require-returns": "off", + }, + }, + { + files: "**/*.md/*.ts", + rules: { + "n/no-missing-import": [ + "error", + { allowModules: ["create-typescript-app"] }, + ], + }, + }, + { + excludedFiles: ["**/*.md/*.ts"], + extends: [ + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + ], + files: ["**/*.ts"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.eslint.json", + }, + rules: { + // These off-by-default rules work well for this repo and we like them on. + "deprecation/deprecation": "error", + + // These more-strict-by-default rules don't work well for this repo and we like them less strict. + "@typescript-eslint/no-unnecessary-condition": [ + "error", + { + allowConstantLoopConditions: true, + }, + ], + "@typescript-eslint/prefer-nullish-coalescing": [ + "error", + { ignorePrimitives: true }, + ], + }, + }, + { + excludedFiles: ["package.json"], + extends: ["plugin:jsonc/recommended-with-json"], + files: ["*.json", "*.jsonc"], + parser: "jsonc-eslint-parser", + rules: { + "jsonc/sort-keys": "error", + }, + }, + { + files: ["*.jsonc"], + rules: { + "jsonc/no-comments": "off", + }, + }, + { + files: "**/*.test.ts", + rules: { + // These on-by-default rules aren't useful in test files. + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + }, + }, + { + extends: ["plugin:yml/standard", "plugin:yml/prettier"], + files: ["**/*.{yml,yaml}"], + parser: "yaml-eslint-parser", + rules: { + "yml/file-extension": ["error", { extension: "yml" }], + "yml/sort-keys": [ + "error", + { + order: { type: "asc" }, + pathPattern: "^.*$", + }, + ], + "yml/sort-sequence-values": [ + "error", + { + order: { type: "asc" }, + pathPattern: "^.*$", + }, + ], + }, + }, + ], + parser: "@typescript-eslint/parser", + plugins: [ + "@typescript-eslint", + "deprecation", + "jsdoc", + "no-only-tests", + "perfectionist", + "regexp", + "vitest", + ], + reportUnusedDisableDirectives: true, + root: true, + rules: { + // These off/less-strict-by-default rules work well for this repo and we like them on. + "@typescript-eslint/no-unused-vars": ["error", { caughtErrors: "all" }], + "no-only-tests/no-only-tests": "error", + + // These on-by-default rules don't work well for this repo and we like them off. + "no-case-declarations": "off", + "no-constant-condition": "off", + "no-inner-declarations": "off", + "no-mixed-spaces-and-tabs": "off", + + // Stylistic concerns that don't interfere with Prettier + "@typescript-eslint/padding-line-between-statements": [ + "error", + { blankLine: "always", next: "*", prev: "block-like" }, + ], + "perfectionist/sort-objects": [ + "error", + { + order: "asc", + "partition-by-comment": true, + type: "natural", + }, + ], + }, +}; diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..fa256b83 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +github@joshuakgoldberg.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..43e081aa --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,97 @@ +# Contributing + +Thanks for your interest in contributing to `create-typescript-app`! 💖 + +> After this page, see [DEVELOPMENT.md](./DEVELOPMENT.md) for local development instructions. + +## Code of Conduct + +This project contains a [Contributor Covenant code of conduct](./CODE_OF_CONDUCT.md) all contributors are expected to follow. + +## Reporting Issues + +Please do [report an issue on the issue tracker](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/new/choose) if there's any bugfix, documentation improvement, or general enhancement you'd like to see in the repository! Please fully fill out all required fields in the most appropriate issue form. + +## Sending Contributions + +Sending your own changes as contribution is always appreciated! +There are two steps involved: + +1. [Finding an Issue](#finding-an-issue) +2. [Sending a Pull Request](#sending-a-pull-request) + +### Finding an Issue + +With the exception of very small typos, all changes to this repository generally need to correspond to an [open issue marked as `accepting prs` on the issue tracker](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22accepting+prs%22). +If this is your first time contributing, consider searching for [unassigned issues that also have the `good first issue` label](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22accepting+prs%22+label%3A%22good+first+issue%22+no%3Aassignee). +If the issue you'd like to fix isn't found on the issue, see [Reporting Issues](#reporting-issues) for filing your own (please do!). + +#### Issue Claiming + +We don't use any kind of issue claiming system. +We've found in the past that they result in accidental ["licked cookie"](https://devblogs.microsoft.com/oldnewthing/20091201-00/?p=15843) situations where contributors claim an issue but run out of time or energy trying before sending a PR. + +If an issue has been marked as `accepting prs` and an open PR does not exist, feel free to send a PR. +Please don't post comments asking for permission or stating you will work on an issue. + +### Sending a Pull Request + +Once you've identified an open issue accepting PRs that doesn't yet have a PR sent, you're free to send a pull request. +Be sure to fill out the pull request template's requested information -- otherwise your PR will likely be closed. + +PRs are also expected to have a title that adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0). +Only PR titles need to be in that format, not individual commits. +Don't worry if you get this wrong: you can always change the PR title after sending it. +Check [previously merged PRs](https://github.com/JoshuaKGoldberg/create-typescript-app/pulls?q=is%3Apr+is%3Amerged+-label%3Adependencies+) for reference. + +#### Draft PRs + +If you don't think your PR is ready for review, [set it as a draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#converting-a-pull-request-to-a-draft). +Draft PRs won't be reviewed. + +#### Granular PRs + +Please keep pull requests single-purpose: in other words, don't attempt to solve multiple unrelated problems in one pull request. +Send one PR per area of concern. +Multi-purpose pull requests are harder and slower to review, block all changes from being merged until the whole pull request is reviewed, and are difficult to name well with semantic PR titles. + +#### Pull Request Reviews + +When a PR is not in draft, it's considered ready for review. +Please don't manually `@` tag anybody to request review. +A maintainer will look at it when they're next able to. + +PRs should have passing [GitHub status checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) before review is requested (unless there are explicit questions asked in the PR about any failures). + +#### Asking Questions + +If you need help and/or have a question, posting a comment in the PR is a great way to do so. +There's no need to tag anybody individually. +One of us will drop by and help when we can. + +Please post comments as [line comments](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#adding-line-comments-to-a-pull-request) when possible, so that they can be threaded. +You can [resolve conversations](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#resolving-conversations) on your own when you feel they're resolved - no need to comment explicitly and/or wait for a maintainer. + +#### Requested Changes + +After a maintainer reviews your PR, they may request changes on it. +Once you've made those changes, [re-request review on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review). + +Please try not to force-push commits to PRs that have already been reviewed. +Doing so makes it harder to review the changes. +We squash merge all commits so there's no need to try to preserve Git history within a PR branch. + +Once you've addressed all our feedback by making code changes and/or started a followup discussion, [re-request review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review) from each maintainer whose feedback you addressed. + +Once all feedback is addressed and the PR is approved, we'll ensure the branch is up to date with `main` and merge it for you. + +#### Post-Merge Recognition + +Once your PR is merged, if you haven't yet been added to the [_Contributors_ table in the README.md](../README.md#contributors) for its [type of contribution](https://allcontributors.org/docs/en/emoji-key "Allcontributors emoji key"), you should be soon. +Please do ping the maintainer who merged your PR if that doesn't happen within 24 hours - it was likely an oversight on our end! + +## Emojis & Appreciation + +If you made it all the way to the end, bravo dear user, we love you. +Please include your favorite emoji in the bottom of your issues and PRs to signal to us that you did in fact read this file and are trying to conform to it as best as possible. +💖 is a good starter if you're not sure which to use. diff --git a/.github/DEVELOPMENT.md b/.github/DEVELOPMENT.md new file mode 100644 index 00000000..169c3098 --- /dev/null +++ b/.github/DEVELOPMENT.md @@ -0,0 +1,201 @@ +# Development + +After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + +```shell +git clone https://github.com//create-typescript-app +cd create-typescript-app +pnpm install +``` + +> This repository includes a list of suggested VS Code extensions. +> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development. + +## Building + +Run [**tsup**](https://tsup.egoist.dev) locally to build source files from `src/` into output files in `lib/`: + +```shell +pnpm build +``` + +Add `--watch` to run the builder in a watch mode that continuously cleans and recreates `lib/` as you save files: + +```shell +pnpm build --watch +``` + +## Formatting + +[Prettier](https://prettier.io) is used to format code. +It should be applied automatically when you save files in VS Code or make a Git commit. + +To manually reformat all files, you can run: + +```shell +pnpm format --write +``` + +## Linting + +This package includes several forms of linting to enforce consistent code quality and styling. +Each should be shown in VS Code, and can be run manually on the command-line: + +- `pnpm lint` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files +- `pnpm lint:knip` ([knip](https://github.com/webpro/knip)): Detects unused files, dependencies, and code exports +- `pnpm lint:md` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files +- `pnpm lint:package-json` ([npm-package-json-lint](https://npmpackagejsonlint.org/)): Lints the `package.json` file +- `pnpm lint:packages` ([pnpm dedupe --check](https://pnpm.io/cli/dedupe)): Checks for unnecessarily duplicated packages in the `pnpm-lock.yml` file +- `pnpm lint:spelling` ([cspell](https://cspell.org)): Spell checks across all source files + +## Testing + +[Vitest](https://vitest.dev) is used for tests. +You can run it locally on the command-line: + +```shell +pnpm run test +``` + +Add the `--coverage` flag to compute test coverage and place reports in the `coverage/` directory: + +```shell +pnpm run test --coverage +``` + +Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs. +Calls to `console.log`, `console.warn`, and other console methods will cause a test to fail. + +### Debugging Tests + +This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests. +To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5). + +## Type Checking + +You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + +However, it can be useful to run the TypeScript command-line (`tsc`) to type check all files in `src/`: + +```shell +pnpm tsc +``` + +Add `--watch` to keep the type checker running in a watch mode that updates the display as you save files: + +```shell +pnpm tsc --watch +``` + +## Setup Scripts + +As described in the `README.md` file and `docs/`, this template repository comes with three scripts that can set up an existing or new repository. + +Each follows roughly the same general flow: + +1. `bin/index.ts` uses `bin/mode.ts` to determine which of the three setup scripts to run +2. `readOptions` parses in options from local files, Git commands, npm APIs, and/or files on disk +3. `runOrRestore` wraps the setup script's main logic in a friendly prompt wrapper +4. The setup script wraps each portion of its main logic with `withSpinner` + - Each step of setup logic is generally imported from within `src/steps` +5. A call to `outro` summarizes the results for the user + +> **Warning** +> Each setup script overrides many files in the directory they're run in. +> Make sure to save any changes you want to preserve before running them. + +### The Creation Script + +This template's "creation" script is located in `src/create/`. +You can run it locally with `node bin/index.js --mode create`. +Note that files need to be built with `pnpm run build` beforehand. + +#### Testing the Creation Script + +You can run the end-to-end test for creation locally on the command-line. +Note that the files need to be built with `pnpm run build` beforehand. + +```shell +pnpm run test:create +``` + +That end-to-end test executes `script/create-test-e2e.js`, which: + +1. Runs the creation script to create a new `test-repository` child directory and repository, capturing code coverage +2. Asserts that commands such as `build` and `lint` each pass + +The `pnpm run test:create` script is run in CI to ensure that templating changes are in sync with the template's actual files. +See `.github/workflows/test-create.yml`. + +### The Initialization Script + +This template's "initialization" script is located in `src/initialize/`. +You can run it locally with `pnpm run initialize`. +It uses [`tsx`](https://github.com/esbuild-kit/tsx) so you don't need to build files before running. + +```shell +pnpm run initialize +``` + +#### Testing the Initialization Script + +You can run the end-to-end test for initializing locally on the command-line. +Note that files need to be built with `pnpm run build` beforehand. + +```shell +pnpm run test:initialize +``` + +That end-to-end test executes `script/initialize-test-e2e.js`, which: + +1. Runs the initialization script using `--skip-github-api` and other skip flags +2. Checks that the local repository's files were changed correctly (e.g. removed initialization-only files) +3. Runs `pnpm run lint:knip` to make sure no excess dependencies or files were left over +4. Resets everything +5. Runs initialization a second time, capturing test coverage + +The `pnpm run test:initialize` script is run in CI to ensure that templating changes are in sync with the template's actual files. +See `.github/workflows/test-initialize.yml`. + +### The Migration Script + +This template's "migration" script is located in `src/migrate/`. +Note that files need to be built with `pnpm run build` beforehand. + +To test out the script locally, run it from a different repository's directory: + +```shell +cd ../other-repo +node ../create-typescript-app/bin/migrate.js +``` + +The migration script will work on any directory. +You can try it out in a blank directory with scripts like: + +```shell +cd .. +mkdir temp +cd temp +node ../create-typescript-app/bin/migrate.js +``` + +#### Testing the Migration Script + +You can run the end-to-end test for migrating locally on the command-line: + +```shell +pnpm run test:migrate +``` + +That end-to-end test executes `script/migrate-test-e2e.js`, which: + +1. Runs the migration script using `--skip-github-api` and other skip flags, capturing code coverage +2. Checks that only a small list of allowed files were changed +3. Checks that the local repository's files were changed correctly (e.g. removed initialization-only files) + +The `pnpm run test:migrate` script is run in CI to ensure that templating changes are in sync with the template's actual files. +See `.github/workflows/test-migrate.yml`. + +> Tip: if the migration test is failing in CI and you don't see any errors, try [downloading the full logs](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/using-workflow-run-logs#downloading-logs). +> There'll likely be a list of changed files under a message like _`Oh no! Running the migrate script modified some files:`_. +> You can also try running the test script locally. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..c51a73f8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: JoshuaKGoldberg diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..e9c06c80 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,9 @@ + + + + + + +## Overview + +... diff --git a/.github/ISSUE_TEMPLATE/01-bug.yml b/.github/ISSUE_TEMPLATE/01-bug.yml new file mode 100644 index 00000000..179e277f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug.yml @@ -0,0 +1,33 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Bug Report Checklist + options: + - label: I have tried restarting my IDE and the issue persists. + required: true + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What did you expect to happen? + label: Expected + type: textarea + validations: + required: true + - attributes: + description: What happened instead? + label: Actual + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea +description: Report a bug trying to run the code +labels: + - "type: bug" +name: 🐛 Bug +title: "🐛 Bug: " diff --git a/.github/ISSUE_TEMPLATE/02-documentation.yml b/.github/ISSUE_TEMPLATE/02-documentation.yml new file mode 100644 index 00000000..5ee0fd29 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-documentation.yml @@ -0,0 +1,25 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Bug Report Checklist + options: + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What would you like to report? + label: Overview + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea +description: Report a typo or missing area of documentation +labels: + - "area: documentation" +name: 📝 Documentation +title: "📝 Documentation: " diff --git a/.github/ISSUE_TEMPLATE/03-feature.yml b/.github/ISSUE_TEMPLATE/03-feature.yml new file mode 100644 index 00000000..cef1bc25 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-feature.yml @@ -0,0 +1,27 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Bug Report Checklist + options: + - label: I have tried restarting my IDE and the issue persists. + required: true + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What did you expect to be able to do? + label: Overview + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea +description: Request that a new feature be added or an existing feature improved +labels: + - "type: feature" +name: 🚀 Feature +title: "🚀 Feature: " diff --git a/.github/ISSUE_TEMPLATE/04-tooling.yml b/.github/ISSUE_TEMPLATE/04-tooling.yml new file mode 100644 index 00000000..8814d72d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04-tooling.yml @@ -0,0 +1,27 @@ +body: + - attributes: + description: If any of these required steps are not taken, we may not be able to review your issue. Help us to help you! + label: Bug Report Checklist + options: + - label: I have tried restarting my IDE and the issue persists. + required: true + - label: I have pulled the latest `main` branch of the repository. + required: true + - label: I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue. + required: true + type: checkboxes + - attributes: + description: What did you expect to be able to do? + label: Overview + type: textarea + validations: + required: true + - attributes: + description: Any additional info you'd like to provide. + label: Additional Info + type: textarea +description: Report a bug or request an enhancement in repository tooling +labels: + - "area: tooling" +name: 🛠 Tooling +title: "🛠 Tooling: " diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..dd02f443 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ + + +## PR Checklist + +- [ ] Addresses an existing open issue: fixes #000 +- [ ] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) +- [ ] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/create-typescript-app/blob/main/.github/CONTRIBUTING.md) were taken + +## Overview + + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000..5ea3d619 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +We take all security vulnerabilities seriously. +If you have a vulnerability or other security issues to disclose: + +- Thank you very much, please do! +- Please send them to us by emailing `github@joshuakgoldberg.com` + +We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml new file mode 100644 index 00000000..c15700d5 --- /dev/null +++ b/.github/actions/prepare/action.yml @@ -0,0 +1,14 @@ +description: Prepares the repo for a typical CI job + +name: Prepare + +runs: + steps: + - uses: pnpm/action-setup@v2 + - uses: actions/setup-node@v3 + with: + cache: pnpm + node-version: "18" + - run: pnpm install --frozen-lockfile + shell: bash + using: composite diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 00000000..639a1461 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "automerge": true, + "internalChecksFilter": "strict", + "labels": ["dependencies"], + "minimumReleaseAge": "3 days", + "postUpdateOptions": ["pnpmDedupe"] +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..057fc542 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,16 @@ +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build + - run: node ./lib/index.js + +name: Build + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml new file mode 100644 index 00000000..341d28d5 --- /dev/null +++ b/.github/workflows/compliance.yml @@ -0,0 +1,28 @@ +jobs: + compliance: + runs-on: ubuntu-latest + steps: + - uses: mtfoley/pr-compliance-action@main + with: + body-auto-close: false + ignore-authors: |- + allcontributors + allcontributors[bot] + renovate + renovate[bot] + ignore-team-members: false + +name: Compliance + +on: + pull_request: + branches: + - main + types: + - edited + - opened + - reopened + - synchronize + +permissions: + pull-requests: write diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 00000000..c148046f --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,18 @@ +jobs: + contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/prepare + - env: + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + uses: JoshuaKGoldberg/all-contributors-auto-action@v0.3.2 + +name: Contributors + +on: + push: + branches: + - main diff --git a/.github/workflows/lint-knip.yml b/.github/workflows/lint-knip.yml new file mode 100644 index 00000000..df3f955b --- /dev/null +++ b/.github/workflows/lint-knip.yml @@ -0,0 +1,16 @@ +jobs: + lint_knip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build || true + - run: pnpm lint:knip + +name: Lint Knip + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/lint-markdown.yml b/.github/workflows/lint-markdown.yml new file mode 100644 index 00000000..acac714d --- /dev/null +++ b/.github/workflows/lint-markdown.yml @@ -0,0 +1,15 @@ +jobs: + lint_markdown: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:md + +name: Lint Markdown + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/lint-package-json.yml b/.github/workflows/lint-package-json.yml new file mode 100644 index 00000000..d5a8cd39 --- /dev/null +++ b/.github/workflows/lint-package-json.yml @@ -0,0 +1,15 @@ +jobs: + lint_package_json: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:package-json + +name: Lint Package JSON + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/lint-packages.yml b/.github/workflows/lint-packages.yml new file mode 100644 index 00000000..87520cac --- /dev/null +++ b/.github/workflows/lint-packages.yml @@ -0,0 +1,15 @@ +jobs: + lint_packages: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:packages + +name: Lint Packages + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/lint-spelling.yml b/.github/workflows/lint-spelling.yml new file mode 100644 index 00000000..ef020b62 --- /dev/null +++ b/.github/workflows/lint-spelling.yml @@ -0,0 +1,15 @@ +jobs: + lint_spelling: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:spelling + +name: Lint spelling + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..49b2ba34 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,16 @@ +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build || true + - run: pnpm lint + +name: Lint + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml new file mode 100644 index 00000000..4f649c48 --- /dev/null +++ b/.github/workflows/post-release.yml @@ -0,0 +1,27 @@ +jobs: + post_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: echo "npm_version=$(npm pkg get version | tr -d '"')" >> "$GITHUB_ENV" + - uses: apexskier/github-release-commenter@v1 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + comment-template: | + :tada: This is included in version {release_link} :tada: + + The release is available on: + + * [GitHub releases](https://github.com/JoshuaKGoldberg/create-typescript-app/releases/tag/{release_tag}) + * [npm package (@latest dist-tag)](https://www.npmjs.com/package/create-typescript-app/v/${{ env.npm_version }}) + + Cheers! 📦🚀 + +name: Post Release + +on: + release: + types: + - published diff --git a/.github/workflows/pr-review-requested.yml b/.github/workflows/pr-review-requested.yml new file mode 100644 index 00000000..e2e518c8 --- /dev/null +++ b/.github/workflows/pr-review-requested.yml @@ -0,0 +1,21 @@ +jobs: + pr_review_requested: + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: "status: waiting for author" + - if: failure() + run: | + echo "Don't worry if the previous step failed." + echo "See https://github.com/actions-ecosystem/action-remove-labels/issues/221." + +name: PR Review Requested + +on: + pull_request_target: + types: + - review_requested + +permissions: + pull-requests: write diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 00000000..ae43fc44 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,15 @@ +jobs: + prettier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm format --list-different + +name: Prettier + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..85118097 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,88 @@ +concurrency: + group: ${{ github.workflow }} + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + - uses: ./.github/actions/prepare + - run: pnpm build + - run: git config user.name "${GITHUB_ACTOR}" + - run: git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + - env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN + - name: Delete branch protection on main + uses: actions/github-script@v6.4.1 + with: + github-token: ${{ secrets.ACCESS_TOKEN }} + script: | + try { + await github.request( + `DELETE /repos/JoshuaKGoldberg/create-typescript-app/branches/main/protection`, + ); + } catch (error) { + if (!error.message?.includes?.("Branch not protected")) { + throw error; + } + } + - env: + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: | + if pnpm run should-semantic-release ; then + pnpm release-it --verbose + fi + - if: always() + name: Recreate branch protection on main + uses: actions/github-script@v6.4.1 + with: + github-token: ${{ secrets.ACCESS_TOKEN }} + script: | + github.request( + `PUT /repos/JoshuaKGoldberg/create-typescript-app/branches/main/protection`, + { + allow_deletions: false, + allow_force_pushes: true, + allow_fork_pushes: false, + allow_fork_syncing: true, + block_creations: false, + branch: "main", + enforce_admins: false, + owner: "JoshuaKGoldberg", + repo: "create-typescript-app", + required_conversation_resolution: true, + required_linear_history: false, + required_pull_request_reviews: null, + required_status_checks: { + checks: [ + { context: "build" }, + { context: "compliance" }, + { context: "lint" }, + { context: "lint_knip" }, + { context: "lint_markdown" }, + { context: "lint_package_json" }, + { context: "lint_packages" }, + { context: "lint_spelling" }, + { context: "prettier" }, + { context: "test" }, + ], + strict: false, + }, + restrictions: null, + } + ); + +name: Release + +on: + push: + branches: + - main + +permissions: + contents: write + id-token: write diff --git a/.github/workflows/test-create.yml b/.github/workflows/test-create.yml new file mode 100644 index 00000000..3a055927 --- /dev/null +++ b/.github/workflows/test-create.yml @@ -0,0 +1,28 @@ +jobs: + create: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm run build + - run: pnpm run test:create + - if: always() + name: Codecov + uses: codecov/codecov-action@v3 + with: + files: coverage-create/lcov.info + flags: create + - if: always() + name: Archive code coverage results + uses: actions/upload-artifact@v3 + with: + path: coverage-create + +name: Test Creation Script + +on: + pull_request: ~ + + push: + branches: + - main diff --git a/.github/workflows/test-initialize.yml b/.github/workflows/test-initialize.yml new file mode 100644 index 00000000..ac736a6d --- /dev/null +++ b/.github/workflows/test-initialize.yml @@ -0,0 +1,28 @@ +jobs: + initialize: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm run build + - run: pnpm run test:initialize + - if: always() + name: Codecov + uses: codecov/codecov-action@v3 + with: + files: coverage-initialize/lcov.info + flags: initialize + - if: always() + name: Archive code coverage results + uses: actions/upload-artifact@v3 + with: + path: coverage-initialize + +name: Test Initialization Script + +on: + pull_request: ~ + + push: + branches: + - main diff --git a/.github/workflows/test-migrate.yml b/.github/workflows/test-migrate.yml new file mode 100644 index 00000000..30e99073 --- /dev/null +++ b/.github/workflows/test-migrate.yml @@ -0,0 +1,28 @@ +jobs: + migrate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm run build + - run: pnpm run test:migrate + - if: always() + name: Codecov + uses: codecov/codecov-action@v3 + with: + files: coverage-migrate/lcov.info + flags: migrate + - if: always() + name: Archive code coverage results + uses: actions/upload-artifact@v3 + with: + path: coverage-migrate + +name: Test Migration Script + +on: + pull_request: ~ + + push: + branches: + - main diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..48af8220 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm run test --coverage + - name: Codecov + uses: codecov/codecov-action@v3 + with: + flags: unit + - if: always() + name: Archive code coverage results + uses: actions/upload-artifact@v3 + with: + name: code-coverage-report + path: coverage + +name: Test + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml new file mode 100644 index 00000000..3b20f242 --- /dev/null +++ b/.github/workflows/tsc.yml @@ -0,0 +1,15 @@ +jobs: + type_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm tsc + +name: Type Check + +on: + pull_request: ~ + push: + branches: + - main diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..32ffbca9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +coverage*/ +lib/ +node_modules/ diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 00000000..31354ec1 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..0ccfe480 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,3 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" +npx lint-staged diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..14f350bf --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,5 @@ +{ + "extends": "markdownlint/style/prettier", + "first-line-h1": false, + "no-inline-html": false +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..d6ea7b60 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,4 @@ +.github/CODE_OF_CONDUCT.md +CHANGELOG.md +lib/ +node_modules/ diff --git a/.npmpackagejsonlintrc.json b/.npmpackagejsonlintrc.json new file mode 100644 index 00000000..60d8a205 --- /dev/null +++ b/.npmpackagejsonlintrc.json @@ -0,0 +1,4 @@ +{ + "extends": "npm-package-json-lint-config-default", + "rules": { "require-description": "error", "require-license": "error" } +} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..02c8b485 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.18.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..613eedd4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +.all-contributorsrc +coverage*/ +lib/ +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..19bc8199 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/prettierrc", + "overrides": [ + { "files": ".*rc", "options": { "parser": "json" } }, + { "files": ".nvmrc", "options": { "parser": "yaml" } } + ], + "plugins": ["prettier-plugin-curly", "prettier-plugin-packagejson"], + "useTabs": true +} diff --git a/.release-it.json b/.release-it.json new file mode 100644 index 00000000..2b95facc --- /dev/null +++ b/.release-it.json @@ -0,0 +1,18 @@ +{ + "git": { + "commitMessage": "chore: release v${version}", + "requireCommits": true + }, + "github": { + "autoGenerate": true, + "release": true, + "releaseName": "v${version}" + }, + "npm": { "publishArgs": ["--access public", "--provenance"] }, + "plugins": { + "@release-it/conventional-changelog": { + "infile": "CHANGELOG.md", + "preset": "angular" + } + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..095512a0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "DavidAnson.vscode-markdownlint", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "streetsidesoftware.code-spell-checker" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..dedf0160 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "args": ["run", "${relativeFile}"], + "autoAttachChildProcesses": true, + "console": "integratedTerminal", + "name": "Debug Current Test File", + "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", + "request": "launch", + "skipFiles": ["/**", "**/node_modules/**"], + "smartStep": true, + "type": "node" + } + ], + "version": "0.2.0" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e700d4d4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.rulers": [80], + "eslint.probe": [ + "javascript", + "javascriptreact", + "json", + "jsonc", + "markdown", + "typescript", + "typescriptreact", + "yaml" + ], + "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }], + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..bf7dd773 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,139 @@ +## [1.29.52](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.29.51...v1.29.52) (2023-09-21) + +### Bug Fixes + +- generate CHANGELOG.md files again ([#854](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/854)) ([88fbfb9](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/88fbfb92f2cb16a93fe7f7badbb40c8e069de468)), closes [#842](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/842) + +# Changelog + +### [1.27.12](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.11...v1.27.12) (2022-12-31) + +### [1.27.11](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.10...v1.27.11) (2022-12-29) + +### [1.27.10](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.9...v1.27.10) (2022-12-26) + +### [1.27.9](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.8...v1.27.9) (2022-12-26) + +### Bug Fixes + +- run build prior to release ([#166](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/166)) ([c83355b](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/c83355bafb5196dcb4716ced032ddc7e5b02d072)) + +### [1.27.8](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.7...v1.27.8) (2022-12-25) + +### [1.27.7](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.6...v1.27.7) (2022-12-24) + +### [1.27.6](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.5...v1.27.6) (2022-12-24) + +### [1.27.5](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.4...v1.27.5) (2022-12-24) + +### Bug Fixes + +- don't skipCi for allcontributors ([#152](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/152)) ([674e2c1](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/674e2c1ada116f0b04b7a137fa3bfe2f9f34d558)) + +### [1.27.4](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.3...v1.27.4) (2022-12-24) + +### [1.27.3](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.2...v1.27.3) (2022-12-23) + +### [1.27.2](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.1...v1.27.2) (2022-12-23) + +### [1.27.1](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.27.0...v1.27.1) (2022-12-23) + +## [1.27.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.26.0...v1.27.0) (2022-12-23) + +### Features + +- document 145, and re-add npm publishing ([4df06c6](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/4df06c6dd4092b041d29eefe573a021a62a40e0d)) + +## [1.26.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.22.1...v1.26.0) (2022-12-23) + +### Features + +- fix typo in pnpm description ([e1a21fc](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/e1a21fc62b3ba9d2421099b3e4db3eab33b301f0)) +- re-add branch protection toggling in release.yml ([#144](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/144)) ([b55395a](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/b55395a962dca3cc24cfb5590a9107f527c94855)) +- skip npm publish (just for testing ([14dc939](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/14dc939aafd648cd6d36587b5378b540da87770d)) +- try adding back benjefferies/branch-protection-bot ([7e12306](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/7e12306dffb37fb1e8545ec2ee7c7db970d26434)) +- use Octokit to populate more settings ([#130](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/130)) ([ff33d79](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/ff33d796ee95f802262f413ba88af4e3237c1425)) + +### Bug Fixes + +- correct renovate config ([#133](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/133)) ([19f7666](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/19f766641d7d7cfa896134b398bc7431f200e087)) +- unlock base branch for PRs ([e0fa0c9](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/e0fa0c9b19caf1f7a6fff12a64917a58851db5d4)) + +### [1.22.1](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.22.0...v1.22.1) (2022-12-20) + +### Bug Fixes + +- only append README.md notice once in setup ([#131](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/131)) ([bd2901d](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/bd2901d11f9d1c942d3bea816208845c47deff58)) + +## [1.22.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.21.0...v1.22.0) (2022-12-15) + +### Features + +- add header to CHANGELOG.md ([#127](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/127)) ([4724643](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/4724643fb33de89b33e437af5e340d9fe2eb39c6)) + +## [1.21.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.20.2...v1.21.0) (2022-12-15) + +### Features + +- clear CHANGELOG.md and local Git tags in setup ([#125](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/125)) ([9ed5f45](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/9ed5f452f1de3841647cfff594676819a3e416e5)) + +## [1.20.2](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.20.1...v1.20.2) (2022-12-15) + +### Bug Fixes + +- corrected allcontributors setup import path with fs ([#124](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/124)) ([2506907](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/25069072073b3b26b039aaf2f6b6dea9cd1cd8b2)) + +## [1.20.1](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.20.0...v1.20.1) (2022-12-15) + +### Bug Fixes + +- corrected description quote in setup script ([#123](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/123)) ([1360510](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/1360510ee2173084499b5d4360d6213b8003e76c)) + +## [1.20.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.19.0...v1.20.0) (2022-12-15) + +### Features + +- add 'area: testing' label to setup data ([#120](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/120)) ([e0f9e0b](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/e0f9e0b34f44db996400932185cd2359cb29bba2)) + +## [1.19.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.8.0...v1.19.0) (2022-12-15) + +### Features + +- bind console.log always to the same value ([#111](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/111)) ([c62dc71](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/c62dc7134408bb726af16550499a560fe117dee6)) +- remove branch protection bot from release.yml ([c8b5e2b](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/c8b5e2b0eea3c57e0ce6d46e0f53b09eb20feed8)) +- set back release.yml ([27fa842](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/27fa842449a3ea119a5b90fe5648c104872b30e8)) +- testing out package version 1.14.0 bump ([69e2617](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/69e26170c7dc95bfcc5a9ab997156f1cdfb93a9c)) + +### Bug Fixes + +- temporarily disable branch protections for release-it ([#113](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/113)) ([e4c41b8](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/e4c41b8a18ea7d4fab1fff151213519c94596367)) + +## [1.8.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.7.0...v1.8.0) (2022-12-14) + +### Features + +- switch from semantic-release to release-it ([#110](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/110)) ([c0fcbf4](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/c0fcbf46718ee6ad0e3582b1f01e2910a3da847d)) + +## [1.7.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.6.0...v1.7.0) (2022-12-14) + +### Features + +- used semantic-release/exec to clean changelog headings ([#109](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/109)) ([db88e12](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/db88e12afd863020d46f9aaec12a4ef1d824c94b)) + +## [1.6.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.5.0...v1.6.0) (2022-12-13) + +### Features + +- change GHANGELOG.md title to include hash ([297eb4e](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/297eb4edf9187d7f38d03e3be2daf169f05fe8a4)) + +## [1.5.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.4.0...v1.5.0) (2022-12-13) + +### Features + +- add test entry to CHANGELOG.md ([c39b3db](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/c39b3db1ad2bf8cd9eb2939ac1d3bba848a2f3d5)) + +## [1.4.0](https://github.com/JoshuaKGoldberg/create-typescript-app/compare/v1.3.0...v1.4.0) (2022-12-13) + +### Features + +- bind console log for performance (not really) ([#105](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/105)) ([98155c5](https://github.com/JoshuaKGoldberg/create-typescript-app/commit/98155c5fdb301c78bc9a04a5933b4843fb186692)) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..08520a1e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +# MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..cfee81b0 --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +

Create TypeScript App

+ +

Quickstart-friendly TypeScript template with comprehensive, configurable, opinionated tooling. 💝

+ +

+ + + +All Contributors: 29 🤝 + + + + + Codecov Test Coverage + + + Code of Conduct: Enforced 🤝 + + + License: MIT 📝 + + + Sponsor: On GitHub 💸 + + Style: Prettier 🧹 + TypeScript: Strict 💪 + npm package version +

+ +Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS' + +`create-typescript-app` is a one-stop-shop solution to set up a new or existing repository with the latest and greatest TypeScript tooling. +It includes options not just for building and testing but also GitHub repository templates, contributor recognition, automated release management, and more. + +## Getting Started + +First make sure you have the following installed: + +- [GitHub CLI](https://cli.github.com) _(you'll need to be logged in)_ +- [Node.js](https://nodejs.org) +- [pnpm](https://pnpm.io) + +Then in an existing repository or in your directory where you'd like to make a new repository: + +```shell +npx create-typescript-app +``` + +That setup script will walk you through using the template. +You can read more about the supported setup modes in their docs pages: + +- [**Creating from the terminal**](./docs/Creation.md): creating a new repository locally on the command-line _(recommended)_ +- [**Initializing from the template**](./docs/Initialization.md): creating a new repository with the [_Use this template_](https://github.com/JoshuaKGoldberg/create-typescript-app/generate) button on GitHub +- [**Migrating an existing repository**](./docs/Migration.md): adding this template's tooling on top of an existing repository + +## Documentation + +You can read more about `create-typescript-app` and the tooling it supports: + +1. [**Tooling**](./docs/Tooling.md): a breakdown of all the pieces this template can set up. +2. [**Options**](./docs/Options.md): granular options to customize how the template is run. +3. [**FAQs**](./docs/FAQs.md): frequently asked questions + +> [!NOTE] +> This template is early stage, opinionated, and not endorsed by the TypeScript team. +> It can be configured to set up a _lot_ of tooling out of the box. +> If you don't want to use any particular tool, you can always remove it manually. + +## Development + +See [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md), then [`.github/DEVELOPMENT.md`](./.github/DEVELOPMENT.md). +Thanks! 💖 + +## Contributors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Anurag
Anurag

💻
Conor Meagher
Conor Meagher

💻
Daniel
Daniel

🚇
Daniel Roe
Daniel Roe

💻
Dominik Nowik
Dominik Nowik

🔧 💻
Emerson
Emerson

💻
Jeff Wen
Jeff Wen

💻
Jessica Wilkins
Jessica Wilkins

💻
Joe Previte
Joe Previte

🐛 💻
John Reilly
John Reilly

💻
Josh Goldberg
Josh Goldberg

🐛 💻 🚧 👀 🔧 📖 🚇 ⚠️
Joël Galeran
Joël Galeran

💻
Juan A.
Juan A.

💻
Kristo Baricevic
Kristo Baricevic

💻
Lars Kappert
Lars Kappert

💻
Navin Moorthy
Navin Moorthy

🐛 💻
NazCodeland
NazCodeland

💻
Orta Therox
Orta Therox

💻
Paul Esch-Laurent
Paul Esch-Laurent

💻
Promise Dash
Promise Dash

💻
Rebecca Stevens
Rebecca Stevens

💻 🚇
Ron Braha
Ron Braha

💻 🎨 ⚠️
Ron Jean-Francois
Ron Jean-Francois

💻 🚇
Ruthwik
Ruthwik

💻
Ryota Murakami
Ryota Murakami

💻 🐛
Sudhansu
Sudhansu

💻
Tung Bui (Leo)
Tung Bui (Leo)

💻
Vasanth Kumar Cheepurupalli
Vasanth Kumar Cheepurupalli

💻
takanomedev
takanomedev

💻
+ + + + + + diff --git a/bin/index.js b/bin/index.js new file mode 100755 index 00000000..1b7a2843 --- /dev/null +++ b/bin/index.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +import { bin } from "../lib/bin/index.js"; + +process.exitCode = await bin(process.argv.slice(2)); diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..70976385 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +codecov: + notify: + after_n_builds: 4 +comment: + after_n_builds: 4 diff --git a/create-typescript-app.png b/create-typescript-app.png new file mode 100644 index 00000000..592fb48a Binary files /dev/null and b/create-typescript-app.png differ diff --git a/cspell.json b/cspell.json new file mode 100644 index 00000000..7dc76f62 --- /dev/null +++ b/cspell.json @@ -0,0 +1,38 @@ +{ + "dictionaries": ["typescript"], + "ignorePaths": [ + "./coverage*", + ".github", + "CHANGELOG.md", + "lib", + "node_modules", + "pnpm-lock.yaml" + ], + "words": [ + "allcontributors", + "apexskier", + "Codecov", + "codespace", + "commitlint", + "contributorsrc", + "conventionalcommits", + "execa", + "infile", + "joshuakgoldberg", + "knip", + "lcov", + "markdownlintignore", + "mtfoley", + "npmignore", + "npmjs", + "npmpackagejsonlintrc", + "outro", + "packagejson", + "quickstart", + "tada", + "tsup", + "Unstaged", + "wontfix", + "vitest" + ] +} diff --git a/docs/Creation.md b/docs/Creation.md new file mode 100644 index 00000000..15d8be8a --- /dev/null +++ b/docs/Creation.md @@ -0,0 +1,32 @@ +# Creating from the Terminal + +You can run `npx create-typescript-app` in your terminal to interactively create a new repository in a child directory: + +```shell +npx create-typescript-app +``` + +Then, go through the following two steps to set up required repository tooling on GitHub: + +1. Create two tokens in [repository secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) _(unless you chose to opt out of releases)_: + - `ACCESS_TOKEN`: A [GitHub PAT](https://github.com/settings/tokens/new) with _repo_ and _workflow_ permissions + - `NPM_TOKEN`: An [npm access token](https://docs.npmjs.com/creating-and-viewing-access-tokens/) with _Automation_ permissions +2. Install two GitHub apps: + - [Codecov](https://github.com/marketplace/codecov) _(unless you chose to opt out of tests)_ + - [Renovate](https://github.com/marketplace/renovate) _(unless you chose to opt out of renovate)_ + +Your new repository will then be ready for development! +Hooray! 🥳 + +## Options + +You can explicitly provide some or all of the options the script would prompt for as command-line flags. +See [Options.md](./Options.md). + +For example, running the creation script and skipping all GitHub APIs: + +```shell +npx create-typescript-app --mode create --skip-all-contributors-api --skip-github-api +``` + +See [Tooling.md](./Tooling.md) for details on the tooling pieces and which bases they're included in. diff --git a/docs/FAQs.md b/docs/FAQs.md new file mode 100644 index 00000000..f56123d8 --- /dev/null +++ b/docs/FAQs.md @@ -0,0 +1,53 @@ +# FAQs + +## Can I use _(insert tool here)_ with this template? + +Yes! +After you set up a repository, you can substitute in any tools you'd like. + +If you think the tool would be broadly useful to most consumers of this template, feel free to [file a feature request](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/new?assignees=&labels=type%3A+feature&projects=&template=03-feature.yml&title=%F0%9F%9A%80+Feature%3A+%3Cshort+description+of+the+feature%3E) to add it in. + +## Is there a way to pull in template updates to previously created repositories? + +Not directly. +You can always copy & paste them in manually, and/or re-run `npx create-typescript-app --mode migrate`. + +See [🚀 Feature: Add a script to sync the tooling updates from forked template repo #498](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/498): it will likely eventually be possible. + +### What about `eslint-config-prettier`? + +[`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) is an ESLint plugin that serves only to turn off all rules that are unnecessary or might conflict with formatters such as Prettier. +None of the ESLint configs enabled by this repository's tooling leave any rules enabled that would need to be disabled. +Using `eslint-config-prettier` would be redundant. + +### What determines which "base" a tool goes into? + +The four bases correspond to what have seemed to be the most common user needs of template consumers: + +1. **Minimum**: Developers who just want the barest of starting templates. + - They may be very new to TypeScript tooling, or they may have made an informed decision that the additional tooling isn't worth the complexity and/or time investment. + - Tooling in this base is only what would be essential for a small TypeScript package that can be built, formatted, linted, and released. +2. **Common**: The common case of users who want the minimum tooling along with common repository management. + - Tooling added in this base should be essential for a TypeScript repository that additionally automates useful GitHub tasks: contributor recognition, release management, and testing. +3. **Everything**: Power users (including this repository) who want as much of the latest and greatest safety checks and standardization as possible. + +Note that users can always customize exactly with portions are kept in with `--base` **`prompt`**. + +### Which tools can't I remove? + +The following pieces of this template's tooling don't have options to be removed: + +- Linting with ESLint and `pnpm run lint` +- GitHub repository metadata such as the code of conduct and issue templates +- Prettier and `pnpm run format` +- tsup and `pnpm run build` +- TypeScript and `pnpm run tsc` + +If you have a strong desire to add an `--exclude-*` flag for any of them, please do [file a feature request](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/new?assignees=&labels=type%3A+feature&projects=&template=03-feature.yml&title=%F0%9F%9A%80+Feature%3A+%3Cshort+description+of+the+feature%3E). + +## Why does this package include so many tools? + +This repository is meant to serve as a starter that includes all the general tooling a modern TypeScript/Node repository could possibly need. +Each of the included tools exists for a good reason and provides real value. + +If you don't want to use any particular tool, you can always remove it manually. diff --git a/docs/Initialization.md b/docs/Initialization.md new file mode 100644 index 00000000..4512f609 --- /dev/null +++ b/docs/Initialization.md @@ -0,0 +1,46 @@ +# Initializing from the Template + +As an alternative to [creating with `npx create-typescript-app`](./Creation.md), the [_Use this template_](https://github.com/JoshuaKGoldberg/create-typescript-app/generate) button on GitHub can be used to quickly create a new repository from the template. +You can set up the new repository locally by cloning it and installing packages: + +```shell +git clone https://github.com/YourUsername/YourRepositoryName +cd YourRepositoryName +pnpm i +``` + +> 💡 If you don't want to clone it locally, you can always [develop in a codespace](https://docs.github.com/en/codespaces/developing-in-codespaces/developing-in-a-codespace) instead. + +Once the repository's packages are installed, you can run `pnpm run initialize` to fill out your repository's details and install necessary packages. +It will then remove itself and uninstall dependencies only used for initialization. + +```shell +pnpm run initialize +``` + +Then, go through the following two steps to set up required repository tooling on GitHub: + +1. Create two tokens in [repository secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) _(unless you chose to opt out of releases)_: + - `ACCESS_TOKEN`: A [GitHub PAT](https://github.com/settings/tokens/new) with _repo_ and _workflow_ permissions + - `NPM_TOKEN`: An [npm access token](https://docs.npmjs.com/creating-and-viewing-access-tokens/) with _Automation_ permissions +2. Install two GitHub apps: + - [Codecov](https://github.com/marketplace/codecov) _(unless you chose to opt out of tests)_ + - [Renovate](https://github.com/marketplace/renovate) _(unless you chose to opt out of renovate)_ + +Your new repository will then be ready for development! +Hooray! 🥳 + +## Options + +You can explicitly provide some or all of the options the script would prompt for as command-line flags. +See [Options.md](./Options.md). + +`pnpm run initialize` will set `--mode` to `initialize`. + +For example, running the initialization script and skipping all GitHub APIs: + +```shell +pnpm run initialize --skip-all-contributors-api --skip-github-api +``` + +See [Tooling.md](./Tooling.md) for details on the tooling pieces and which bases they're included in. diff --git a/docs/Migration.md b/docs/Migration.md new file mode 100644 index 00000000..0c2b006a --- /dev/null +++ b/docs/Migration.md @@ -0,0 +1,25 @@ +# Migrating an Existing Repository + +If you have an existing repository that you'd like to give the files from this repository, you can run `npx create-typescript-app` in it to "migrate" its tooling to this template's. + +```shell +npx create-typescript-app +``` + +> [!WARNING] +> Migration will override many files in your repository. +> You'll want to review each of the changes. +> There will almost certainly be some incorrect changes you'll need to fix. + +## Options + +You can explicitly provide some or all of the options the script would prompt for as command-line flags. +See [Options.md](./Options.md). + +For example, running the migration script and skipping all GitHub APIs: + +```shell +npx create-typescript-app --mode migrate --skip-all-contributors-api --skip-github-api +``` + +See [Tooling.md](./Tooling.md) for details on the tooling pieces and which bases they're included in. diff --git a/docs/Options.md b/docs/Options.md new file mode 100644 index 00000000..83a8538d --- /dev/null +++ b/docs/Options.md @@ -0,0 +1,157 @@ +# Options + +All three of `create-typescript-app`'s setup scripts -[creation](./Creation.md), [initialization](./Initialization.md), and [migration](./Migration.md)- support a shared set of input options. + +> This page uses `npx create-typescript-app` in its code examples, but initialization's `pnpm run initialize` works the same. + +## Required Options + +The following required options will be prompted for interactively if not provided as CLI flags. + +### Base and Mode + +These required options determine how the creation script will set up and scaffold the repository: + +- `--base`: Whether to scaffold the repository with: + - `minimum`: Just the bare starter tooling most repositories should ideally include. + - `common`: Important additions to the minimum starters such as releases and tests. + - `everything`: The most thorough tooling imaginable: sorting, spellchecking, and more! + - `prompt`: Fine-grained control over which tooling pieces to use +- `--create-repository` _(boolean)_: Whether to create a corresponding repository on github.com (if it doesn't yet exist) +- `--mode`: Whether to: + - `create` a new repository in a child directory + - `initialize` a freshly repository in the current directory + - `migrate` an existing repository in the current directory + +For example, scaffolding a full new repository under the current directory and also linking it to a new repository on github.com: + +```shell +npx create-typescript-app --base everything --create-repository --mode create +``` + +See [Tooling.md](./Tooling.md) for details on the tooling pieces and which bases they're included in. + +### Core Options + +These required options determine the options that will be substituted into the template's files: + +- `--description` _(`string`)_: Sentence case description of the repository (e.g. `Quickstart-friendly TypeScript package with lots of great repository tooling. ✨`) +- `--owner` _(`string`)_: GitHub organization or user the repository is underneath (e.g. `JoshuaKGoldberg`) +- `--repository` _(`string`)_: The kebab-case name of the repository (e.g. `create-typescript-app`) +- `--title` _(`string`)_: Title Case title for the repository to be used in documentation (e.g. `Create TypeScript App`) + +For example, pre-populating all core required options and also creating a new repository: + +```shell +npx create-typescript-app --create-repository --base everything --mode create --repository testing-repository --title "Testing Title" --owner TestingOwner --description "Test Description" +``` + +That script will run completely autonomously, no prompted inputs required. ✨ + +## Optional Options + +The setup scripts also allow for optional overrides of the following inputs whose defaults are based on other options: + +- `--access` _(`"public" | "restricted"`)_: Which [`npm publish --access`](https://docs.npmjs.com/cli/commands/npm-publish#access) to release npm packages with (by default, `"public"`) +- `--author` _(`string`)_: Username on npm to publish packages under (by default, an existing npm author, or the currently logged in npm user, or `owner.toLowerCase()`) +- `--email` _(`string`)_: Email address to be listed as the point of contact in docs and packages (e.g. `example@joshuakgoldberg.com`) + - Optionally, `--email-github` _(`string`)_ and/or `--email-npm` _(`string`)_ may be provided to use different emails in `.md` files and `package.json`, respectively +- `--funding` _(`string`)_: GitHub organization or username to mention in `funding.yml` (by default, `owner`) +- `--keywords` _(`string[]`)_: Any number of keywords to include in `package.json` (by default, none) + - This can be specified any number of times, like `--keywords apple --keywords "banana cherry"` +- `--logo` _(`string`)_: Local image file in the repository to display near the top of the README.md as a logo + - `--logo-alt` _(`string`)_: If `--logo` is provided or detected from an existing README.md, alt text that describes the image will be prompted for if not provided + +For example, customizing the ownership and users associated with a new repository: + +```shell +npx create-typescript-app --author my-npm-username --email example@joshuakgoldberg.com --funding MyGitHubOrganization +``` + +> 💡 You can always manually edit files such as `package.json` after running a setup script. + +## Opt-Outs + +The setup scripts can be directed with CLI flags to opt out tooling portions and/or using API calls. + +### Excluding Tooling Portions + +The setup scripts normally will prompt you to select how much of the tooling you'd like to enable in a new repository. +Alternately, you can bypass that prompt by providing any number of the following CLI flags: + +- `--exclude-all-contributors`: Don't add all-contributors to track contributions and display them in a README.md table. +- `--exclude-compliance`: Don't add a GitHub Actions workflow to verify that PRs match an expected format. +- `--exclude-lint-json`: Don't apply linting and sorting to `*.json` and `*.jsonc` files. +- `--exclude-lint-knip`: Don't add Knip to detect unused files, dependencies, and code exports. +- `--exclude-lint-md`: Don't apply linting to `*.md` files. +- `--exclude-lint-package-json`: Don't add npm-package-json-lint to lint for package.json correctness. +- `--exclude-lint-deprecation`: Don't use eslint-plugin-deprecation to report on usage of code marked as `@deprecated`. +- `--exclude-lint-eslint`: Don't use eslint-plugin-eslint-comment to enforce good practices around ESLint comment directives. +- `--exclude-lint-jsdoc`: Don't use eslint-plugin-jsdoc to enforce good practices around JSDoc comments. +- `--exclude-lint-packages`: Don't add a pnpm dedupe workflow to ensure packages aren't duplicated unnecessarily. +- `--exclude-lint-perfectionist`: Don't apply eslint-plugin-perfectionist to ensure imports, keys, and so on are in sorted order. +- `--exclude-lint-regex`: Don't add eslint-plugin-regex to enforce good practices around regular expressions. +- `--exclude-lint-strict`: Don't augment the recommended logical lint rules with typescript-eslint's strict config. +- `--exclude-lint-stylistic`: Don't add stylistic rules such as typescript-eslint's stylistic config. +- `--exclude-lint-spelling`: Don't add cspell to spell check against dictionaries of known words. +- `--exclude-lint-yml`: Don't apply linting and sorting to `*.yaml` and `*.yml` files. +- `--exclude-releases`: Don't add release-it to generate changelogs, package bumps, and publishes based on conventional commits. +- `--exclude-renovate`: Don't add a Renovate config to dependencies up-to-date with PRs. +- `--exclude-tests`: Don't add Vitest tooling for fast unit tests, configured with coverage tracking. + +For example, initializing with all tooling except for `package.json` checks and Renovate: + +```shell +npx create-typescript-app --exclude-lint-package-json --exclude-lint-packages --exclude-renovate +``` + +> **Warning** +> Specifying any `--exclude-*` flag on the command-line will cause the setup script to skip prompting for more excludes. + +See [Tooling.md](./Tooling.md) for details on the tooling pieces and which bases they're included in. + +### Skipping API Calls + +> Alternately, see [Offline Mode](#offline-mode) to skip API calls without disabling features + +You can prevent the migration script from making some network-based changes using any or all of the following CLI flags: + +- `--skip-all-contributors-api` _(`boolean`)_: Skips network calls that fetch all-contributors data from GitHub + - This flag does nothing if `--exclude-all-contributors` was specified. +- `--skip-github-api` _(`boolean`)_: Skips calling to GitHub APIs. +- `--skip-install` _(`boolean`)_: Skips installing all the new template packages with `pnpm`. + +For example, providing all three flags will completely skip all network requests: + +```shell +npx create-typescript-app --skip-all-contributors-api --skip-github-api --skip-install +``` + +> 💡 Tip: To temporarily preview what the script would apply without making changes on GitHub, you can run with all `--skip-*-api` flags, then `git add -A; git reset --hard HEAD` to completely reset all changes. + +### Skipping Local Changes + +You can prevent the migration script from making some changes on disk using any or all of the following CLI flags: + +- `--skip-removal` _(`boolean`)_: Skips removing setup docs and scripts, including this `docs/` directory +- `--skip-restore` _(`boolean`)_: Skips the prompt offering to restore the repository if an error occurs during setup +- `--skip-uninstall` _(`boolean`)_: Skips uninstalling packages only used for setup scripts + +For example, providing all local change skip flags: + +```shell +npx create-typescript-app --skip-removal --skip-restore --skip-uninstall +``` + +## Offline Mode + +You can run `create-typescript-app` in an "offline" mode with `--offline`. +Doing so will: + +- Enable `--exclude-all-contributors-api` and `--skip-github-api` +- Skip network calls when setting up contributors +- Run pnpm commands with pnpm's `--offline` mode + +```shell +npx create-typescript-app --offline +``` diff --git a/docs/Tooling.md b/docs/Tooling.md new file mode 100644 index 00000000..dd48da85 --- /dev/null +++ b/docs/Tooling.md @@ -0,0 +1,302 @@ +# Tooling + +`create-typescript-app` provides over two dozen pieces of tooling, ranging from code building and formatting to various forms of GitHub repository management. +Most can be individually turned off or on. + +The `create-typescript-app` setup scripts -[creation](./Creation.md), [initialization](./Initialization.md), and [migration](./Migration.md)- will prompt you to choose a "base" template level to initialize from. +Those template levels provide common presets of which tooling pieces to enable. + +```plaintext +◆ How much tooling would you like the template to set up for you? +│ ○ minimum Just the bare starter tooling most repositories should ideally include. +│ ○ common Important additions to the minimum starters such as releases and tests. +│ ○ everything The most thorough tooling imaginable: sorting, spellchecking, and more! +│ ○ prompt (allow me to customize) +└ +``` + +This table summarizes each tooling piece and which base levels they're included in: + +| Tooling Piece | Exclusion Flag | Minimum | Common | Everything | +| --------------------------------------------- | ------------------------------ | ------- | ------ | ---------- | +| [Building](#building) | | ✔️ | ✅ | 💯 | +| [Compliance](#compliance) | `--exclude-compliance` | | | 💯 | +| [Contributors](#contributors) | `--exclude-contributors` | | ✅ | 💯 | +| [Formatting](#formatting) | | ✔️ | ✅ | 💯 | +| [Lint Deprecation](#lint-deprecation) | `--exclude-lint-deprecation` | | | 💯 | +| [Lint ESLint](#lint-eslint) | `--exclude-lint-eslint` | | | 💯 | +| [Lint JSDoc](#lint-jsdoc) | `--exclude-lint-jsdoc` | | | 💯 | +| [Lint JSON](#lint-json) | `--exclude-lint-json` | | | 💯 | +| [Lint Knip](#lint-knip) | `--exclude-lint-knip` | | ✅ | 💯 | +| [Lint MD](#lint-md) | `--exclude-lint-md` | | | 💯 | +| [Lint Package JSON](#lint-package-json) | `--exclude-lint-package-json` | | | 💯 | +| [Lint Packages](#lint-packages) | `--exclude-lint-packages` | | | 💯 | +| [Lint Perfectionist](#lint-perfectionist) | `--exclude-lint-perfectionist` | | | 💯 | +| [Lint Regex](#lint-regex) | `--exclude-lint-regex` | | | 💯 | +| [Lint Spelling](#lint-spelling) | `--exclude-lint-spelling` | | | 💯 | +| [Lint Strict](#lint-strict) | `--exclude-lint-strict` | | | 💯 | +| [Lint Stylistic](#lint-stylistic) | `--exclude-lint-stylistic` | | | 💯 | +| [Lint YML](#lint-yml) | `--exclude-lint-yml` | | | 💯 | +| [Linting](#linting) | | ✔️ | ✅ | 💯 | +| [Package Management](#package-management) | | ✔️ | ✅ | 💯 | +| [Releases](#releases) | `--exclude-releases` | | ✅ | 💯 | +| [Renovate](#renovate) | `--exclude-renovate` | | ✅ | 💯 | +| [Repository Templates](#repository-templates) | | ✔️ | ✅ | 💯 | +| [Testing](#testing) | `--exclude-tests` | | ✅ | 💯 | +| [Type Checking](#type-checking) | | ✔️ | ✅ | 💯 | + +See also [Options](./Options.md) for how to customize the way template is run. + +## "Minimum" Base Level + +These tooling pieces are the ones that most repositories should generally always have enabled. +Other pieces of tooling are likely to not work as well (or at all) if these are removed. + +The _"minimum"_ base is best suited for projects that are very small and not likely to change very frequently. +However, they'll be missing out on many of the great tooling pieces enabled in more comprehensive bases. +We strongly recommend using at least the [_"common"_ base level](#common-base-level) instead for most repositories. + +- [Building](#building) +- [Formatting](#formatting) +- [Linting](#linting) +- [Package Management](#package-management) +- [Repository Templates](#repository-templates) +- [Type Checking](#type-checking) + +### Building + +[**tsup**](https://tsup.egoist.dev): Builds output definitions and JavaScript files using [esbuild](https://esbuild.github.io). +Each `*.ts` source file within `src/` is built into `.d.ts`, `.js`, and `.js.map` output files in `lib/`. + +Building once: + +```shell +pnpm run build +``` + +Building in watch mode: + +```shell +pnpm run build --watch +``` + +### Formatting + +[**Prettier**](https://prettier.io): Formats code for developers and enforces a consistent formatting style. +It's run on file save per [VS Code](TODO) settings and as a Git commit hook via [husky](https://typicode.github.io/husky) and [lint-staged](https://github.com/okonet/lint-staged). + +Auto-formatting all files: + +```shell +pnpm run format --write +``` + +### Linting + +[**ESLint**](https://eslint.org): Static analysis for JavaScript code that detects likely logical issues and helps enforce consistent code style. +Uses [**typescript-eslint**](https://typescript-eslint.io) to understand TypeScript syntax and include TypeScript-specific rules. + +Linting all files: + +```shell +pnpm run lint +``` + +Linting all files, auto-fixing fixable rule reports: + +```shell +pnpm run lint --fix +``` + +### Package Management + +[**pnpm**](https://pnpm.io): Disk-efficient package manager alternative to npm. +It caches packages in a computer-wide registry and symlinks installed packages within that registry. + +Installing packages: + +```shell +pnpm run install +``` + +### Repository Templates + +In code, assorted repository documentation files for GitHub are created: + +- [Code of conduct](TODO) +- [Contributing guide](TODO) +- [Issue templates](TODO) +- [PR template](TODO) +- [Security policy](TODO) + +[GitHub Actions workflows](TODO) are added for each of the enabled tooling pieces to run them in CI. + +On the GitHub repository, metadata will be populated: + +- [Issue labels](TODO) for issue areas, statuses, and types. +- [Repository settings](TODO) such as [branch protections](TODO) and [squash merging PRs](TODO) + +### Type Checking + +[**TypeScript**](https://typescriptlang.org): A typed superset of JavaScript that ensures runtime behavior matches the code's intent. +Configured with strict compiler options, including [`noImplicitAny`](https://aka.ms#noImplicitAny) and [`strict`](https://aka.ms#strict). + +Type checking once: + +```shell +pnpm run tsc +``` + +Type checking in watch mode: + +```shell +pnpm run tsc --watch +``` + +## "Common" Base Level + +These added tooling pieces are those that aren't quite essential for a repository, but are still very commonly useful. +This is recommended for most users of `create-typescript-app` to start with. + +- [Contributors](#contributors) +- [Lint Knip](#lint-knip) +- [Releases](#releases) +- [Renovate](#renovate) +- [Testing](#testing) + +### Contributors + +[**All Contributors**](https://allcontributors.org): Tracks various kinds of contributions and displays them in a nicely formatted table in the README.md. +Contributions will be auto-detected when possible using [all-contributors-auto-action](https://github.com/JoshuaKGoldberg/all-contributors-auto-action). + +### Lint Knip + +[**Knip**](https://github.com/webpro/knip): Detects unused files, dependencies, and code exports. + +Running Knip: + +```shell +pnpm run lint:knip +``` + +### Releases + +[**release-it**](https://github.com/release-it/release-it): Generates changelogs, bumps the package version, and publishes to GitHub and npm based on [conventional commits](https://www.conventionalcommits.org). + +### Renovate + +[**Renovate**](https://docs.renovatebot.com): Keeps dependencies up-to-date with PRs, configured to wait a few days after each update for safety. + +### Testing + +[**Vitest**](https://vitest.dev): Fast unit tests, configured with coverage tracking. + +Additionally: + +- [`console-fail-test`](https://github.com/JoshuaKGoldberg/console-fail-test) will also be added to ensure tests don't accidentally log to the console. +- [`eslint-plugin-no-only-tests`](TODO) will be added to the ESLint config to ensure calls to `it.only` or similar are not checked in +- [`eslint-plugin-vitest`](TODO) will be added to the ESLint config to lint for Vitest-specific issues + +Running tests in watch mode: + +```shell +pnpm run test +``` + +Running tests in watch mode and auto-updating [Vitest snapshots](TODO): + +```shell +pnpm run test -u +``` + +Running tests once with coverage tracking: + +```shell +pnpm run test run --coverage +``` + +## "Everything" Base Level + +This level is for developers who are eager to get the maximum tooling benefits in a repository. +Using the _"everything"_ level will gain you comprehensive, strict coverage of all sorts of repository issues, including auto-sorting of properties and strict ESLint configs. + +- [Compliance](#compliance) +- [Lint Deprecation](#lint-deprecation) +- [Lint ESLint](#lint-eslint) +- [Lint JSDoc](#lint-jsdoc) +- [Lint JSON](#lint-json) +- [Lint MD](#lint-md) +- [Lint Package JSON](#lint-package-json) +- [Lint Packages](#lint-packages) +- [Lint Perfectionist](#lint-perfectionist) +- [Lint Regex](#lint-regex) +- [Lint Spelling](#lint-spelling) +- [Lint Strict](#lint-strict) +- [Lint Stylistic](#lint-stylistic) +- [Lint YML](#lint-yml) + +### Compliance + +[**PR Compliance Action**](https://github.com/mtfoley/pr-compliance-action): Checks PRs for compliance such as addressing a linked issue and proper title formatting. + +### Lint Deprecation + +[`eslint-plugin-deprecation`](https://github.com/gund/eslint-plugin-deprecation): Reports on usage of code marked with `@deprecated`. + +### Lint ESLint + +[`eslint-plugin-eslint-comments`](https://github.com/mysticatea/eslint-plugin-eslint-comments): Enforces proper usage of [ESLint configuration comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments). + +### Lint JSDoc + +[`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc): Enforces good practices around JSDoc comments. + +### Lint JSON + +[`eslint-plugin-jsdoc`](https://github.com/ota-meshi/eslint-plugin-jsonc): Adds linting for `.json` and `.jsonc` files. + +### Lint MD + +[**Markdownlint**](https://github.com/DavidAnson/markdownlint): Linting for Markdown code. + +> This is a separate linter from ESLint, but will likely eventually be switched to an ESLint plugin ([#567](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/567)). + +### Lint Package JSON + +[`npm-package-json-lint`](https://github.com/tclindner/npm-package-json-lint): Linting for `package.json` files. + +> This is a separate linter from ESLint, but will likely eventually be switched to an ESLint plugin ([#839](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/839)). + +### Lint Packages + +Uses [`pnpm dedupe`](https://pnpm.io/cli/dedupe) to deduplicate + +> This is grouped with _"Lint"_ tooling pieces, but will likely eventually be renamed ([#896](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/896)). + +### Lint Perfectionist + +[`eslint-plugin-perfectionist`](https://eslint-plugin-perfectionist.azat.io): Lints for sorting properties, imports, etc. +This plugin is quite particular -perfectionist, even- but all its rules include auto-fixers that can correct complaints for you. + +### Lint Regex + +[`eslint-plugin-regex`](https://github.com/gmullerb/eslint-plugin-regex): Detects issues with JavaScript regular expressions, such as potential exponential complexity. + +### Lint Spelling + +[**CSpell**](https://cspell.org): Spell checking for code. +Helps detect typos based on a configurable user dictionary (`cspell.json`). + +> This is a separate linter from ESLint, but will likely eventually be switched to an ESLint plugin ([#897](https://github.com/JoshuaKGoldberg/create-typescript-app/issues/897)). + +### Lint Strict + +Enables [typescript-eslint's strict configs](https://typescript-eslint.io/linting/configs/#strict) for increased scrutiny around code logic. + +### Lint Stylistic + +Enables [typescript-eslint's stylistic configs](https://typescript-eslint.io/linting/configs/#stylistic) for increased scrutiny around consistent code style. + +### Lint YML + +[`eslint-plugin-yml`](https://ota-meshi.github.io/eslint-plugin-yml): Adds linting for `yaml` and `.yml` files, such as sorting keys. diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 00000000..83c85370 --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,16 @@ +{ + "$schema": "https://unpkg.com/knip@latest/schema.json", + "entry": [ + "src/index.ts!", + "src/guide/index.ts", + "src/initialize/index.ts", + "src/migrate/index.ts", + "script/*e2e.js" + ], + "ignoreBinaries": ["gh"], + "ignoreExportsUsedInFile": { + "interface": true, + "type": true + }, + "project": ["src/**/*.ts!", "script/**/*.js"] +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..2281e006 --- /dev/null +++ b/package.json @@ -0,0 +1,115 @@ +{ + "name": "create-typescript-app", + "version": "1.30.2", + "description": "Quickstart-friendly TypeScript template with comprehensive, configurable, opinionated tooling. 💝", + "repository": { + "type": "git", + "url": "https://github.com/JoshuaKGoldberg/create-typescript-app" + }, + "license": "MIT", + "author": { + "name": "Josh Goldberg", + "email": "npm@joshuakgoldberg.com" + }, + "type": "module", + "main": "./lib/index.js", + "bin": "./bin/index.js", + "files": [ + "bin/", + "lib/", + "package.json", + "LICENSE.md", + "README.md" + ], + "scripts": { + "build": "tsup", + "format": "prettier \"**/*\" --ignore-unknown", + "initialize": "tsx ./src/bin/index.js --mode initialize", + "lint": "eslint . .*js --max-warnings 0", + "lint:knip": "knip", + "lint:md": "markdownlint \"**/*.md\" \".github/**/*.md\" --rules sentences-per-line", + "lint:package-json": "npmPkgJsonLint .", + "lint:packages": "pnpm dedupe --check", + "lint:spelling": "cspell \"**\" \".github/**/*\"", + "prepare": "husky install", + "should-semantic-release": "should-semantic-release --verbose", + "test": "vitest", + "test:create": "node script/create-test-e2e.js", + "test:initialize": "node script/initialize-test-e2e.js", + "test:migrate": "node script/migrate-test-e2e.js", + "tsc": "tsc" + }, + "lint-staged": { + "*": "prettier --ignore-unknown --write" + }, + "dependencies": { + "@clack/prompts": "^0.7.0", + "all-contributors-for-repository": "^0.1.0", + "chalk": "^5.3.0", + "execa": "^8.0.1", + "git-remote-origin-url": "^4.0.0", + "git-url-parse": "^13.1.0", + "js-yaml": "^4.1.0", + "lazy-value": "^3.0.0", + "npm-user": "^5.0.1", + "octokit": "^3.1.0", + "prettier": "^3.0.2", + "replace-in-file": "^7.0.1", + "title-case": "^3.0.3", + "zod": "^3.22.2", + "zod-validation-error": "^1.5.0" + }, + "devDependencies": { + "@octokit/request-error": "^5.0.0", + "@release-it/conventional-changelog": "^7.0.1", + "@types/eslint": "^8.44.2", + "@types/git-url-parse": "^9.0.1", + "@types/js-yaml": "^4.0.5", + "@types/node": "^20.5.6", + "@types/prettier": "^2.7.3", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", + "@vitest/coverage-v8": "^0.34.3", + "c8": "^8.0.1", + "console-fail-test": "^0.2.3", + "cspell": "^7.0.1", + "eslint": "^8.48.0", + "eslint-plugin-deprecation": "^2.0.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jsdoc": "^46.5.0", + "eslint-plugin-jsonc": "^2.9.0", + "eslint-plugin-markdown": "^3.0.1", + "eslint-plugin-n": "^16.0.2", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-perfectionist": "^2.0.0", + "eslint-plugin-regexp": "^1.15.0", + "eslint-plugin-vitest": "^0.3.0", + "eslint-plugin-yml": "^1.8.0", + "globby": "^13.2.2", + "husky": "^8.0.3", + "jsonc-eslint-parser": "^2.3.0", + "knip": "2.29.0", + "lint-staged": "^14.0.1", + "markdownlint": "^0.31.0", + "markdownlint-cli": "^0.37.0", + "npm-package-json-lint": "^7.0.0", + "npm-package-json-lint-config-default": "^6.0.0", + "prettier-plugin-curly": "^0.1.2", + "prettier-plugin-packagejson": "^2.4.5", + "release-it": "^16.1.5", + "sentences-per-line": "^0.2.1", + "should-semantic-release": "^0.1.1", + "tsup": "^7.2.0", + "tsx": "^3.12.7", + "typescript": "^5.1.6", + "vitest": "^0.34.3", + "yaml-eslint-parser": "^1.2.2" + }, + "packageManager": "pnpm@8.8.0", + "engines": { + "node": ">=18" + }, + "publishConfig": { + "provenance": true + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..179b5b12 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7703 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@clack/prompts': + specifier: ^0.7.0 + version: 0.7.0 + all-contributors-for-repository: + specifier: ^0.1.0 + version: 0.1.0 + chalk: + specifier: ^5.3.0 + version: 5.3.0 + execa: + specifier: ^8.0.1 + version: 8.0.1 + git-remote-origin-url: + specifier: ^4.0.0 + version: 4.0.0 + git-url-parse: + specifier: ^13.1.0 + version: 13.1.0 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + lazy-value: + specifier: ^3.0.0 + version: 3.0.0 + npm-user: + specifier: ^5.0.1 + version: 5.0.1 + octokit: + specifier: ^3.1.0 + version: 3.1.0 + prettier: + specifier: ^3.0.2 + version: 3.0.2 + replace-in-file: + specifier: ^7.0.1 + version: 7.0.1 + title-case: + specifier: ^3.0.3 + version: 3.0.3 + zod: + specifier: ^3.22.2 + version: 3.22.2 + zod-validation-error: + specifier: ^1.5.0 + version: 1.5.0(zod@3.22.2) + +devDependencies: + '@octokit/request-error': + specifier: ^5.0.0 + version: 5.0.0 + '@release-it/conventional-changelog': + specifier: ^7.0.1 + version: 7.0.1(release-it@16.1.5) + '@types/eslint': + specifier: ^8.44.2 + version: 8.44.2 + '@types/git-url-parse': + specifier: ^9.0.1 + version: 9.0.1 + '@types/js-yaml': + specifier: ^4.0.5 + version: 4.0.5 + '@types/node': + specifier: ^20.5.6 + version: 20.5.6 + '@types/prettier': + specifier: ^2.7.3 + version: 2.7.3 + '@typescript-eslint/eslint-plugin': + specifier: ^6.5.0 + version: 6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2) + '@typescript-eslint/parser': + specifier: ^6.5.0 + version: 6.5.0(eslint@8.48.0)(typescript@5.2.2) + '@vitest/coverage-v8': + specifier: ^0.34.3 + version: 0.34.3(vitest@0.34.3) + c8: + specifier: ^8.0.1 + version: 8.0.1 + console-fail-test: + specifier: ^0.2.3 + version: 0.2.3 + cspell: + specifier: ^7.0.1 + version: 7.0.1 + eslint: + specifier: ^8.48.0 + version: 8.48.0 + eslint-plugin-deprecation: + specifier: ^2.0.0 + version: 2.0.0(eslint@8.48.0)(typescript@5.2.2) + eslint-plugin-eslint-comments: + specifier: ^3.2.0 + version: 3.2.0(eslint@8.48.0) + eslint-plugin-jsdoc: + specifier: ^46.5.0 + version: 46.5.0(eslint@8.48.0) + eslint-plugin-jsonc: + specifier: ^2.9.0 + version: 2.9.0(eslint@8.48.0) + eslint-plugin-markdown: + specifier: ^3.0.1 + version: 3.0.1(eslint@8.48.0) + eslint-plugin-n: + specifier: ^16.0.2 + version: 16.0.2(eslint@8.48.0) + eslint-plugin-no-only-tests: + specifier: ^3.1.0 + version: 3.1.0 + eslint-plugin-perfectionist: + specifier: ^2.0.0 + version: 2.0.1(eslint@8.48.0)(typescript@5.2.2) + eslint-plugin-regexp: + specifier: ^1.15.0 + version: 1.15.0(eslint@8.48.0) + eslint-plugin-vitest: + specifier: ^0.3.0 + version: 0.3.1(@typescript-eslint/eslint-plugin@6.5.0)(eslint@8.48.0)(vitest@0.34.3) + eslint-plugin-yml: + specifier: ^1.8.0 + version: 1.8.0(eslint@8.48.0) + globby: + specifier: ^13.2.2 + version: 13.2.2 + husky: + specifier: ^8.0.3 + version: 8.0.3 + jsonc-eslint-parser: + specifier: ^2.3.0 + version: 2.3.0 + knip: + specifier: 2.29.0 + version: 2.29.0 + lint-staged: + specifier: ^14.0.1 + version: 14.0.1 + markdownlint: + specifier: ^0.31.0 + version: 0.31.1 + markdownlint-cli: + specifier: ^0.37.0 + version: 0.37.0 + npm-package-json-lint: + specifier: ^7.0.0 + version: 7.0.0 + npm-package-json-lint-config-default: + specifier: ^6.0.0 + version: 6.0.0(npm-package-json-lint@7.0.0) + prettier-plugin-curly: + specifier: ^0.1.2 + version: 0.1.2(prettier@3.0.2) + prettier-plugin-packagejson: + specifier: ^2.4.5 + version: 2.4.5(prettier@3.0.2) + release-it: + specifier: ^16.1.5 + version: 16.1.5 + sentences-per-line: + specifier: ^0.2.1 + version: 0.2.1 + should-semantic-release: + specifier: ^0.1.1 + version: 0.1.1 + tsup: + specifier: ^7.2.0 + version: 7.2.0(typescript@5.2.2) + tsx: + specifier: ^3.12.7 + version: 3.12.7 + typescript: + specifier: ^5.1.6 + version: 5.2.2 + vitest: + specifier: ^0.34.3 + version: 0.34.3 + yaml-eslint-parser: + specifier: ^1.2.2 + version: 1.2.2 + +packages: + + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@babel/code-frame@7.22.5: + resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.5 + + /@babel/generator@7.22.5: + resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + jsesc: 2.5.2 + dev: true + + /@babel/helper-environment-visitor@7.22.5: + resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-function-name@7.22.5: + resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.5 + '@babel/types': 7.22.5 + dev: true + + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 + dev: true + + /@babel/helper-split-export-declaration@7.22.5: + resolution: {integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.5 + dev: true + + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + engines: {node: '>=6.9.0'} + + /@babel/highlight@7.22.5: + resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + + /@babel/parser@7.22.5: + resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.22.5 + dev: true + + /@babel/template@7.22.5: + resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.5 + '@babel/parser': 7.22.5 + '@babel/types': 7.22.5 + dev: true + + /@babel/traverse@7.22.5: + resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.5 + '@babel/generator': 7.22.5 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.5 + '@babel/parser': 7.22.5 + '@babel/types': 7.22.5 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types@7.22.5: + resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + to-fast-properties: 2.0.0 + dev: true + + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@clack/core@0.3.3: + resolution: {integrity: sha512-5ZGyb75BUBjlll6eOa1m/IZBxwk91dooBWhPSL67sWcLS0zt9SnswRL0l26TVdBhb0wnWORRxUn//uH6n4z7+A==} + dependencies: + picocolors: 1.0.0 + sisteransi: 1.0.5 + dev: false + + /@clack/prompts@0.7.0: + resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==} + dependencies: + '@clack/core': 0.3.3 + picocolors: 1.0.0 + sisteransi: 1.0.5 + dev: false + bundledDependencies: + - is-unicode-supported + + /@cspell/cspell-bundled-dicts@7.0.1: + resolution: {integrity: sha512-Rm3AAOhZBPWy3L9lYRPQ41HAPP/jKBzTAkDVCsmT3SDbF1R1e7uqzQ86KhLWgcRfqGIh1uLcLjcUOAAh6jLu6Q==} + engines: {node: '>=16'} + dependencies: + '@cspell/dict-ada': 4.0.2 + '@cspell/dict-aws': 4.0.0 + '@cspell/dict-bash': 4.1.1 + '@cspell/dict-companies': 3.0.20 + '@cspell/dict-cpp': 5.0.4 + '@cspell/dict-cryptocurrencies': 3.0.1 + '@cspell/dict-csharp': 4.0.2 + '@cspell/dict-css': 4.0.6 + '@cspell/dict-dart': 2.0.3 + '@cspell/dict-django': 4.1.0 + '@cspell/dict-docker': 1.1.7 + '@cspell/dict-dotnet': 5.0.0 + '@cspell/dict-elixir': 4.0.3 + '@cspell/dict-en-common-misspellings': 1.0.2 + '@cspell/dict-en-gb': 1.1.33 + '@cspell/dict-en_us': 4.3.6 + '@cspell/dict-filetypes': 3.0.1 + '@cspell/dict-fonts': 4.0.0 + '@cspell/dict-fsharp': 1.0.0 + '@cspell/dict-fullstack': 3.1.5 + '@cspell/dict-gaming-terms': 1.0.4 + '@cspell/dict-git': 2.0.0 + '@cspell/dict-golang': 6.0.2 + '@cspell/dict-haskell': 4.0.1 + '@cspell/dict-html': 4.0.3 + '@cspell/dict-html-symbol-entities': 4.0.0 + '@cspell/dict-java': 5.0.5 + '@cspell/dict-k8s': 1.0.1 + '@cspell/dict-latex': 4.0.0 + '@cspell/dict-lorem-ipsum': 4.0.0 + '@cspell/dict-lua': 4.0.1 + '@cspell/dict-node': 4.0.2 + '@cspell/dict-npm': 5.0.8 + '@cspell/dict-php': 4.0.2 + '@cspell/dict-powershell': 5.0.2 + '@cspell/dict-public-licenses': 2.0.3 + '@cspell/dict-python': 4.1.7 + '@cspell/dict-r': 2.0.1 + '@cspell/dict-ruby': 5.0.0 + '@cspell/dict-rust': 4.0.1 + '@cspell/dict-scala': 5.0.0 + '@cspell/dict-software-terms': 3.2.1 + '@cspell/dict-sql': 2.1.1 + '@cspell/dict-svelte': 1.0.2 + '@cspell/dict-swift': 2.0.1 + '@cspell/dict-typescript': 3.1.1 + '@cspell/dict-vue': 3.0.0 + dev: true + + /@cspell/cspell-json-reporter@7.0.1: + resolution: {integrity: sha512-qOnGvnkV4s84X4LncR9F8e9TD2Y+0Yt1GJgsThul8Zgr90qjPpdUnfIwvptByXv7OWOuImpYk66NQIVTQtpcvQ==} + engines: {node: '>=16'} + dependencies: + '@cspell/cspell-types': 7.0.1 + dev: true + + /@cspell/cspell-pipe@7.0.1: + resolution: {integrity: sha512-qbQkBS1xsJfwRFzrPLFE1jDt2MRRG75GKxKmFskNxuE5kdmshQT9/hjs+O/HUgPnNH2+l+aK/S5yisFti3YYoA==} + engines: {node: '>=16'} + dev: true + + /@cspell/cspell-resolver@7.0.1: + resolution: {integrity: sha512-GfaYy+17l8cdZk8wEzp6UxA3hV4th/OsvQnUERMGSQ6oN1j8Rn1aEGUD3xxjhFAK2+AOeB3gx8RyIHQLWgE80g==} + engines: {node: '>=16'} + dependencies: + global-dirs: 3.0.1 + dev: true + + /@cspell/cspell-service-bus@7.0.1: + resolution: {integrity: sha512-rtN4HyW8eHnrTNSji1DEM0v810sqhIIh6Tuo8aNNVoEuUMVdE+L17PoVnMc2dAp6VMv2nvTnh4Lpfsj5l5NsZw==} + engines: {node: '>=16'} + dev: true + + /@cspell/cspell-types@7.0.1: + resolution: {integrity: sha512-nPQGIwVUxNqAhBmSsnvRSJtHUo3cSQiCRpppNaXY8s1IrJ2kskS+LEF+d4SGTjQbCQH39sf3NoFWSCTfjl1jFg==} + engines: {node: '>=16'} + dev: true + + /@cspell/dict-ada@4.0.2: + resolution: {integrity: sha512-0kENOWQeHjUlfyId/aCM/mKXtkEgV0Zu2RhUXCBr4hHo9F9vph+Uu8Ww2b0i5a4ZixoIkudGA+eJvyxrG1jUpA==} + dev: true + + /@cspell/dict-aws@4.0.0: + resolution: {integrity: sha512-1YkCMWuna/EGIDN/zKkW+j98/55mxigftrSFgsehXhPld+ZMJM5J9UuBA88YfL7+/ETvBdd7mwW6IwWsC+/ltQ==} + dev: true + + /@cspell/dict-bash@4.1.1: + resolution: {integrity: sha512-8czAa/Mh96wu2xr0RXQEGMTBUGkTvYn/Pb0o+gqOO1YW+poXGQc3gx0YPqILDryP/KCERrNvkWUJz3iGbvwC2A==} + dev: true + + /@cspell/dict-companies@3.0.20: + resolution: {integrity: sha512-o13HaqYxkWo20FC5iU9PHKMFexY9D7/XeSj9tvBzy3sEzW324zw5MWEkeDszwmC/GsLZtot+5vopCv6/evRNlA==} + dev: true + + /@cspell/dict-cpp@5.0.4: + resolution: {integrity: sha512-Vmz/CCb2d91ES5juaO8+CFWeTa2AFsbpR8bkCPJq+P8cRP16+37tY0zNXEBSK/1ur4MakaRf76jeQBijpZxw0Q==} + dev: true + + /@cspell/dict-cryptocurrencies@3.0.1: + resolution: {integrity: sha512-Tdlr0Ahpp5yxtwM0ukC13V6+uYCI0p9fCRGMGZt36rWv8JQZHIuHfehNl7FB/Qc09NCF7p5ep0GXbL+sVTd/+w==} + dev: true + + /@cspell/dict-csharp@4.0.2: + resolution: {integrity: sha512-1JMofhLK+4p4KairF75D3A924m5ERMgd1GvzhwK2geuYgd2ZKuGW72gvXpIV7aGf52E3Uu1kDXxxGAiZ5uVG7g==} + dev: true + + /@cspell/dict-css@4.0.6: + resolution: {integrity: sha512-2Lo8W2ezHmGgY8cWFr4RUwnjbndna5mokpCK/DuxGILQnuajR0J31ANQOXj/8iZM2phFB93ZzMNk/0c04TDfSQ==} + dev: true + + /@cspell/dict-dart@2.0.3: + resolution: {integrity: sha512-cLkwo1KT5CJY5N5RJVHks2genFkNCl/WLfj+0fFjqNR+tk3tBI1LY7ldr9piCtSFSm4x9pO1x6IV3kRUY1lLiw==} + dev: true + + /@cspell/dict-data-science@1.0.11: + resolution: {integrity: sha512-TaHAZRVe0Zlcc3C23StZqqbzC0NrodRwoSAc8dis+5qLeLLnOCtagYQeROQvDlcDg3X/VVEO9Whh4W/z4PAmYQ==} + dev: true + + /@cspell/dict-django@4.1.0: + resolution: {integrity: sha512-bKJ4gPyrf+1c78Z0Oc4trEB9MuhcB+Yg+uTTWsvhY6O2ncFYbB/LbEZfqhfmmuK/XJJixXfI1laF2zicyf+l0w==} + dev: true + + /@cspell/dict-docker@1.1.7: + resolution: {integrity: sha512-XlXHAr822euV36GGsl2J1CkBIVg3fZ6879ZOg5dxTIssuhUOCiV2BuzKZmt6aIFmcdPmR14+9i9Xq+3zuxeX0A==} + dev: true + + /@cspell/dict-dotnet@5.0.0: + resolution: {integrity: sha512-EOwGd533v47aP5QYV8GlSSKkmM9Eq8P3G/eBzSpH3Nl2+IneDOYOBLEUraHuiCtnOkNsz0xtZHArYhAB2bHWAw==} + dev: true + + /@cspell/dict-elixir@4.0.3: + resolution: {integrity: sha512-g+uKLWvOp9IEZvrIvBPTr/oaO6619uH/wyqypqvwpmnmpjcfi8+/hqZH8YNKt15oviK8k4CkINIqNhyndG9d9Q==} + dev: true + + /@cspell/dict-en-common-misspellings@1.0.2: + resolution: {integrity: sha512-jg7ZQZpZH7+aAxNBlcAG4tGhYF6Ksy+QS5Df73Oo+XyckBjC9QS+PrRwLTeYoFIgXy5j3ICParK5r3MSSoL4gw==} + dev: true + + /@cspell/dict-en-gb@1.1.33: + resolution: {integrity: sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==} + dev: true + + /@cspell/dict-en_us@4.3.6: + resolution: {integrity: sha512-odhgsjNZI9BtEOJdvqfAuv/3yz5aB1ngfBNaph7WSnYVt//9e3fhrElZ6/pIIkoyuGgeQPwz1fXt+tMgcnLSEQ==} + dev: true + + /@cspell/dict-filetypes@3.0.1: + resolution: {integrity: sha512-8z8mY1IbrTyTRumx2vvD9yzRhNMk9SajM/GtI5hdMM2pPpNSp25bnuauzjRf300eqlqPY2MNb5MmhBFO014DJw==} + dev: true + + /@cspell/dict-fonts@4.0.0: + resolution: {integrity: sha512-t9V4GeN/m517UZn63kZPUYP3OQg5f0OBLSd3Md5CU3eH1IFogSvTzHHnz4Wqqbv8NNRiBZ3HfdY/pqREZ6br3Q==} + dev: true + + /@cspell/dict-fsharp@1.0.0: + resolution: {integrity: sha512-dHPkMHwW4dWv3Lv9VWxHuVm4IylqvcfRBSnZ7usJTRThraetSVrOPIJwr6UJh7F5un/lGJx2lxWVApf2WQaB/A==} + dev: true + + /@cspell/dict-fullstack@3.1.5: + resolution: {integrity: sha512-6ppvo1dkXUZ3fbYn/wwzERxCa76RtDDl5Afzv2lijLoijGGUw5yYdLBKJnx8PJBGNLh829X352ftE7BElG4leA==} + dev: true + + /@cspell/dict-gaming-terms@1.0.4: + resolution: {integrity: sha512-hbDduNXlk4AOY0wFxcDMWBPpm34rpqJBeqaySeoUH70eKxpxm+dvjpoRLJgyu0TmymEICCQSl6lAHTHSDiWKZg==} + dev: true + + /@cspell/dict-git@2.0.0: + resolution: {integrity: sha512-n1AxyX5Kgxij/sZFkxFJlzn3K9y/sCcgVPg/vz4WNJ4K9YeTsUmyGLA2OQI7d10GJeiuAo2AP1iZf2A8j9aj2w==} + dev: true + + /@cspell/dict-golang@6.0.2: + resolution: {integrity: sha512-5pyZn4AAiYukAW+gVMIMVmUSkIERFrDX2vtPDjg8PLQUhAHWiVeQSDjuOhq9/C5GCCEZU/zWSONkGiwLBBvV9A==} + dev: true + + /@cspell/dict-haskell@4.0.1: + resolution: {integrity: sha512-uRrl65mGrOmwT7NxspB4xKXFUenNC7IikmpRZW8Uzqbqcu7ZRCUfstuVH7T1rmjRgRkjcIjE4PC11luDou4wEQ==} + dev: true + + /@cspell/dict-html-symbol-entities@4.0.0: + resolution: {integrity: sha512-HGRu+48ErJjoweR5IbcixxETRewrBb0uxQBd6xFGcxbEYCX8CnQFTAmKI5xNaIt2PKaZiJH3ijodGSqbKdsxhw==} + dev: true + + /@cspell/dict-html@4.0.3: + resolution: {integrity: sha512-Gae8i8rrArT0UyG1I6DHDK62b7Be6QEcBSIeWOm4VIIW1CASkN9B0qFgSVnkmfvnu1Y3H7SSaaEynKjdj3cs8w==} + dev: true + + /@cspell/dict-java@5.0.5: + resolution: {integrity: sha512-X19AoJgWIBwJBSWGFqSgHaBR/FEykBHTMjL6EqOnhIGEyE9nvuo32tsSHjXNJ230fQxQptEvRZoaldNLtKxsRg==} + dev: true + + /@cspell/dict-k8s@1.0.1: + resolution: {integrity: sha512-gc5y4Nm3hVdMZNBZfU2M1AsAmObZsRWjCUk01NFPfGhFBXyVne41T7E62rpnzu5330FV/6b/TnFcPgRmak9lLw==} + dev: true + + /@cspell/dict-latex@4.0.0: + resolution: {integrity: sha512-LPY4y6D5oI7D3d+5JMJHK/wxYTQa2lJMSNxps2JtuF8hbAnBQb3igoWEjEbIbRRH1XBM0X8dQqemnjQNCiAtxQ==} + dev: true + + /@cspell/dict-lorem-ipsum@4.0.0: + resolution: {integrity: sha512-1l3yjfNvMzZPibW8A7mQU4kTozwVZVw0AvFEdy+NcqtbxH+TvbSkNMqROOFWrkD2PjnKG0+Ea0tHI2Pi6Gchnw==} + dev: true + + /@cspell/dict-lua@4.0.1: + resolution: {integrity: sha512-j0MFmeCouSoC6EdZTbvGe1sJ9V+ruwKSeF+zRkNNNload7R72Co5kX1haW2xLHGdlq0kqSy1ODRZKdVl0e+7hg==} + dev: true + + /@cspell/dict-node@4.0.2: + resolution: {integrity: sha512-FEQJ4TnMcXEFslqBQkXa5HposMoCGsiBv2ux4IZuIXgadXeHKHUHk60iarWpjhzNzQLyN2GD7NoRMd12bK3Llw==} + dev: true + + /@cspell/dict-npm@5.0.8: + resolution: {integrity: sha512-KuqH8tEsFD6DPKqKwIfWr9E+admE3yghaC0AKXG8jPaf77N0lkctKaS3dm0oxWUXkYKA/eXj6LCtz3VcTyxFPg==} + dev: true + + /@cspell/dict-php@4.0.2: + resolution: {integrity: sha512-7yglcmMoFHDPQXHW+9QAl8YjAToMm1qOi+4x/yGY1FSIEjZbCpjeDgyKMGg/NgpooQQceEN38AR59Pn23EDriA==} + dev: true + + /@cspell/dict-powershell@5.0.2: + resolution: {integrity: sha512-IHfWLme3FXE7vnOmMncSBxOsMTdNWd1Vcyhag03WS8oANSgX8IZ+4lMI00mF0ptlgchf16/OU8WsV4pZfikEFw==} + dev: true + + /@cspell/dict-public-licenses@2.0.3: + resolution: {integrity: sha512-JSLEdpEYufQ1H+93UHi+axlqQm1fhgK6kpdLHp6uPHu//CsvETcqNVawjB+qOdI/g38JTMw5fBqSd0aGNxa6Dw==} + dev: true + + /@cspell/dict-python@4.1.7: + resolution: {integrity: sha512-8GkO7/w1QEpu4Y1GTHGYHrwfc/ZdiBRw7D/BGYCIiOoQPLi0YxMke7wzRC3j246yrzLt28ntDBjr4fB3+uFZtQ==} + dependencies: + '@cspell/dict-data-science': 1.0.11 + dev: true + + /@cspell/dict-r@2.0.1: + resolution: {integrity: sha512-KCmKaeYMLm2Ip79mlYPc8p+B2uzwBp4KMkzeLd5E6jUlCL93Y5Nvq68wV5fRLDRTf7N1LvofkVFWfDcednFOgA==} + dev: true + + /@cspell/dict-ruby@5.0.0: + resolution: {integrity: sha512-ssb96QxLZ76yPqFrikWxItnCbUKhYXJ2owkoIYzUGNFl2CHSoHCb5a6Zetum9mQ/oUA3gNeUhd28ZUlXs0la2A==} + dev: true + + /@cspell/dict-rust@4.0.1: + resolution: {integrity: sha512-xJSSzHDK2z6lSVaOmMxl3PTOtfoffaxMo7fTcbZUF+SCJzfKbO6vnN9TCGX2sx1RHFDz66Js6goz6SAZQdOwaw==} + dev: true + + /@cspell/dict-scala@5.0.0: + resolution: {integrity: sha512-ph0twaRoV+ylui022clEO1dZ35QbeEQaKTaV2sPOsdwIokABPIiK09oWwGK9qg7jRGQwVaRPEq0Vp+IG1GpqSQ==} + dev: true + + /@cspell/dict-software-terms@3.2.1: + resolution: {integrity: sha512-+QXmyoONVc/3aNgKW+0F0u3XUCRTfNRkWKLZQA78i+9fOfde8ZT4JmROmZgRveH/MxD4n6pNFceIRcYI6C8WuQ==} + dev: true + + /@cspell/dict-sql@2.1.1: + resolution: {integrity: sha512-v1mswi9NF40+UDUMuI148YQPEQvWjac72P6ZsjlRdLjEiQEEMEsTQ+zlkIdnzC9QCNyJaqD5Liq9Mn78/8Zxtw==} + dev: true + + /@cspell/dict-svelte@1.0.2: + resolution: {integrity: sha512-rPJmnn/GsDs0btNvrRBciOhngKV98yZ9SHmg8qI6HLS8hZKvcXc0LMsf9LLuMK1TmS2+WQFAan6qeqg6bBxL2Q==} + dev: true + + /@cspell/dict-swift@2.0.1: + resolution: {integrity: sha512-gxrCMUOndOk7xZFmXNtkCEeroZRnS2VbeaIPiymGRHj5H+qfTAzAKxtv7jJbVA3YYvEzWcVE2oKDP4wcbhIERw==} + dev: true + + /@cspell/dict-typescript@3.1.1: + resolution: {integrity: sha512-N9vNJZoOXmmrFPR4ir3rGvnqqwmQGgOYoL1+y6D4oIhyr7FhaYiyF/d7QT61RmjZQcATMa6PSL+ZisCeRLx9+A==} + dev: true + + /@cspell/dict-vue@3.0.0: + resolution: {integrity: sha512-niiEMPWPV9IeRBRzZ0TBZmNnkK3olkOPYxC1Ny2AX4TGlYRajcW0WUtoSHmvvjZNfWLSg2L6ruiBeuPSbjnG6A==} + dev: true + + /@cspell/dynamic-import@7.0.1: + resolution: {integrity: sha512-ILOvieaJ4TspyKmXVDNF89zQxG/EORKAVY5U8HichIchJlQJDHKCxLy9YFJnoWgkAl11oPATImvuiztcDUZoDA==} + engines: {node: '>=16'} + dependencies: + import-meta-resolve: 3.0.0 + dev: true + + /@cspell/strong-weak-map@7.0.1: + resolution: {integrity: sha512-Y2L3kY12J77ETHNtZrfMwfufur2klsl33AqotC+kJ6Kbo2YZ6I3A224G5EBeIbQdmQdkE8KnpLDDcUv5640fJA==} + engines: {node: '>=16'} + dev: true + + /@ericcornelissen/bash-parser@0.5.2: + resolution: {integrity: sha512-4pIMTa1nEFfMXitv7oaNEWOdM+zpOZavesa5GaiWTgda6Zk32CFGxjUp/iIaN0PwgUW1yTq/fztSjbpE8SLGZQ==} + engines: {node: '>=4'} + dependencies: + array-last: 1.3.0 + babylon: 6.18.0 + compose-function: 3.0.3 + deep-freeze: 0.0.1 + filter-iterator: 0.0.1 + filter-obj: 1.1.0 + has-own-property: 0.1.0 + identity-function: 1.0.0 + is-iterable: 1.1.1 + iterable-lookahead: 1.0.0 + lodash.curry: 4.1.1 + magic-string: 0.16.0 + map-obj: 2.0.0 + object-pairs: 0.1.0 + object-values: 1.0.0 + reverse-arguments: 1.0.0 + shell-quote-word: 1.0.1 + to-pascal-case: 1.0.0 + unescape-js: 1.1.4 + dev: true + + /@es-joy/jsdoccomment@0.40.1: + resolution: {integrity: sha512-YORCdZSusAlBrFpZ77pJjc5r1bQs5caPWtAu+WWmiSo+8XaUzseapVrfAtiRFbQWnrBxxLLEwF6f6ZG/UgCQCg==} + engines: {node: '>=16'} + dependencies: + comment-parser: 1.4.0 + esquery: 1.5.0 + jsdoc-type-pratt-parser: 4.0.0 + dev: true + + /@esbuild-kit/cjs-loader@2.4.2: + resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} + dependencies: + '@esbuild-kit/core-utils': 3.1.0 + get-tsconfig: 4.5.0 + dev: true + + /@esbuild-kit/core-utils@3.1.0: + resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==} + dependencies: + esbuild: 0.17.18 + source-map-support: 0.5.21 + dev: true + + /@esbuild-kit/esm-loader@2.5.5: + resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} + dependencies: + '@esbuild-kit/core-utils': 3.1.0 + get-tsconfig: 4.5.0 + dev: true + + /@esbuild/android-arm64@0.17.18: + resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.18.19: + resolution: {integrity: sha512-4+jkUFQxZkQfQOOxfGVZB38YUWHMJX2ihZwF+2nh8m7bHdWXpixiurgGRN3c/KMSwlltbYI0/i929jwBRMFzbA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.17.18: + resolution: {integrity: sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.18.19: + resolution: {integrity: sha512-1uOoDurJYh5MNqPqpj3l/TQCI1V25BXgChEldCB7D6iryBYqYKrbZIhYO5AI9fulf66sM8UJpc3UcCly2Tv28w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.17.18: + resolution: {integrity: sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.18.19: + resolution: {integrity: sha512-ae5sHYiP/Ogj2YNrLZbWkBmyHIDOhPgpkGvFnke7XFGQldBDWvc/AyYwSLpNuKw9UNkgnLlB/jPpnBmlF3G9Bg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.17.18: + resolution: {integrity: sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.18.19: + resolution: {integrity: sha512-HIpQvNQWFYROmWDANMRL+jZvvTQGOiTuwWBIuAsMaQrnStedM+nEKJBzKQ6bfT9RFKH2wZ+ej+DY7+9xHBTFPg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.17.18: + resolution: {integrity: sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.18.19: + resolution: {integrity: sha512-m6JdvXJQt0thNLIcWOeG079h2ivhYH4B5sVCgqb/B29zTcFd7EE8/J1nIUHhdtwGeItdUeqKaqqb4towwxvglQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.17.18: + resolution: {integrity: sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.18.19: + resolution: {integrity: sha512-G0p4EFMPZhGn/xVNspUyMQbORH3nlKTV0bFNHPIwLraBuAkTeMyxNviTe0ZXUbIXQrR1lrwniFjNFU4s+x7veQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.17.18: + resolution: {integrity: sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.18.19: + resolution: {integrity: sha512-hBxgRlG42+W+j/1/cvlnSa+3+OBKeDCyO7OG2ICya1YJaSCYfSpuG30KfOnQHI7Ytgu4bRqCgrYXxQEzy0zM5Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.17.18: + resolution: {integrity: sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.18.19: + resolution: {integrity: sha512-X8g33tczY0GsJq3lhyBrjnFtaKjWVpp1gMq5IlF9BQJ3TUfSK74nQnz9mRIEejmcV+OIYn6bkOJeUaU1Knrljg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.17.18: + resolution: {integrity: sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.18.19: + resolution: {integrity: sha512-qtWyoQskfJlb9MD45mvzCEKeO4uCnDZ7lPFeNqbfaaJHqBiH9qA5Vu2EuckqYZuFMJWy1l4dxTf9NOulCVfUjg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.17.18: + resolution: {integrity: sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.18.19: + resolution: {integrity: sha512-SAkRWJgb+KN+gOhmbiE6/wu23D6HRcGQi15cB13IVtBZZgXxygTV5GJlUAKLQ5Gcx0gtlmt+XIxEmSqA6sZTOw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.17.18: + resolution: {integrity: sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.18.19: + resolution: {integrity: sha512-YLAslaO8NsB9UOxBchos82AOMRDbIAWChwDKfjlGrHSzS3v1kxce7dGlSTsrb0PJwo1KYccypN3VNjQVLtz7LA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.17.18: + resolution: {integrity: sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.18.19: + resolution: {integrity: sha512-vSYFtlYds/oTI8aflEP65xo3MXChMwBOG1eWPGGKs/ev9zkTeXVvciU+nifq8J1JYMz+eQ4J9JDN0O2RKF8+1Q==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.17.18: + resolution: {integrity: sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.18.19: + resolution: {integrity: sha512-tgG41lRVwlzqO9tv9l7aXYVw35BxKXLtPam1qALScwSqPivI8hjkZLNH0deaaSCYCFT9cBIdB+hUjWFlFFLL9A==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.17.18: + resolution: {integrity: sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.18.19: + resolution: {integrity: sha512-EgBZFLoN1S5RuB4cCJI31pBPsjE1nZ+3+fHRjguq9Ibrzo29bOLSBcH1KZJvRNh5qtd+fcYIGiIUia8Jw5r1lQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.17.18: + resolution: {integrity: sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.18.19: + resolution: {integrity: sha512-q1V1rtHRojAzjSigZEqrcLkpfh5K09ShCoIsdTakozVBnM5rgV58PLFticqDp5UJ9uE0HScov9QNbbl8HBo6QQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.17.18: + resolution: {integrity: sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.18.19: + resolution: {integrity: sha512-D0IiYjpZRXxGZLQfsydeAD7ZWqdGyFLBj5f2UshJpy09WPs3qizDCsEr8zyzcym6Woj/UI9ZzMIXwvoXVtyt0A==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.17.18: + resolution: {integrity: sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.18.19: + resolution: {integrity: sha512-3tt3SOS8L3D54R8oER41UdDshlBIAjYhdWRPiZCTZ1E41+shIZBpTjaW5UaN/jD1ENE/Ok5lkeqhoNMbxstyxw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.17.18: + resolution: {integrity: sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.18.19: + resolution: {integrity: sha512-MxbhcuAYQPlfln1EMc4T26OUoeg/YQc6wNoEV8xvktDKZhLtBxjkoeESSo9BbPaGKhAPzusXYj5n8n5A8iZSrA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.17.18: + resolution: {integrity: sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.18.19: + resolution: {integrity: sha512-m0/UOq1wj25JpWqOJxoWBRM9VWc3c32xiNzd+ERlYstUZ6uwx5SZsQUtkiFHaYmcaoj+f6+Tfcl7atuAz3idwQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.17.18: + resolution: {integrity: sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.18.19: + resolution: {integrity: sha512-L4vb6pcoB1cEcXUHU6EPnUhUc4+/tcz4OqlXTWPcSQWxegfmcOprhmIleKKwmMNQVc4wrx/+jB7tGkjjDmiupg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.17.18: + resolution: {integrity: sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.18.19: + resolution: {integrity: sha512-rQng7LXSKdrDlNDb7/v0fujob6X0GAazoK/IPd9C3oShr642ri8uIBkgM37/l8B3Rd5sBQcqUXoDdEy75XC/jg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.17.18: + resolution: {integrity: sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.18.19: + resolution: {integrity: sha512-z69jhyG20Gq4QL5JKPLqUT+eREuqnDAFItLbza4JCmpvUnIlY73YNjd5djlO7kBiiZnvTnJuAbOjIoZIOa1GjA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.48.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.48.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.6.2: + resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.19.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.48.0: + resolution: {integrity: sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array@0.11.10: + resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@hutson/parse-repository-url@5.0.0: + resolution: {integrity: sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==} + engines: {node: '>=10.13.0'} + dev: true + + /@iarna/toml@2.2.5: + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: true + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jest/schemas@29.6.0: + resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/gen-mapping@0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@jridgewell/resolve-uri@3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@ljharb/through@2.3.9: + resolution: {integrity: sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ==} + engines: {node: '>= 0.4'} + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@npmcli/map-workspaces@3.0.4: + resolution: {integrity: sha512-Z0TbvXkRbacjFFLpVpV0e2mheCh+WzQpcqL+4xp49uNJOxOnIAPZyXtUxZ5Qn3QBTGKA11Exjd9a5411rBrhDg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + '@npmcli/name-from-folder': 2.0.0 + glob: 10.3.4 + minimatch: 9.0.3 + read-package-json-fast: 3.0.2 + dev: true + + /@npmcli/name-from-folder@2.0.0: + resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /@octokit/app@14.0.0: + resolution: {integrity: sha512-g/zDXttroZ9Se08shK0d0d/j0cgSA+h4WV7qGUevNEM0piNBkIlfb4Fm6bSwCNAZhNf72mBgERmYOoxicPkqdw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-app': 6.0.0 + '@octokit/auth-unauthenticated': 5.0.0 + '@octokit/core': 5.0.0 + '@octokit/oauth-app': 6.0.0 + '@octokit/plugin-paginate-rest': 8.0.0(@octokit/core@5.0.0) + '@octokit/types': 11.1.0 + '@octokit/webhooks': 12.0.3 + dev: false + + /@octokit/auth-app@6.0.0: + resolution: {integrity: sha512-OKct7Rukf3g9DjpzcpdacQsdmd6oPrJ7fZND22JkjzhDvfhttUOnmh+qPS4kHhaNNyTxqSThnfrUWvkqNLd1nw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-app': 7.0.0 + '@octokit/auth-oauth-user': 4.0.0 + '@octokit/request': 8.1.0 + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + deprecation: 2.3.1 + lru-cache: 10.0.1 + universal-github-app-jwt: 1.1.1 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/auth-oauth-app@7.0.0: + resolution: {integrity: sha512-8JvJEXGoEqrbzLwt3SwIUvkDd+1wrM8up0KawvDIElB8rbxPbvWppGO0SLKAWSJ0q8ILcVq+mWck6pDcZ3a9KA==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-device': 6.0.0 + '@octokit/auth-oauth-user': 4.0.0 + '@octokit/request': 8.1.0 + '@octokit/types': 11.1.0 + '@types/btoa-lite': 1.0.0 + btoa-lite: 1.0.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/auth-oauth-device@6.0.0: + resolution: {integrity: sha512-Zgf/LKhwWk54rJaTGYVYtbKgUty+ouil6VQeRd+pCw7Gd0ECoSWaZuHK6uDGC/HtnWHjpSWFhzxPauDoHcNRtg==} + engines: {node: '>= 18'} + dependencies: + '@octokit/oauth-methods': 4.0.0 + '@octokit/request': 8.1.0 + '@octokit/types': 11.1.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/auth-oauth-user@4.0.0: + resolution: {integrity: sha512-VOm5aIkVGHaOhIvsF/4YmSjoYDzzrKbbYkdSEO0KqHK7I8SlO3ZndSikQ1fBlNPUEH0ve2BOTxLrVvI1qBf9/Q==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-device': 6.0.0 + '@octokit/oauth-methods': 4.0.0 + '@octokit/request': 8.1.0 + '@octokit/types': 11.1.0 + btoa-lite: 1.0.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/auth-token@3.0.2: + resolution: {integrity: sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==} + engines: {node: '>= 14'} + dependencies: + '@octokit/types': 8.0.0 + dev: true + + /@octokit/auth-token@4.0.0: + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/auth-unauthenticated@5.0.0: + resolution: {integrity: sha512-AjOI6FNB2dweJ85p6rf7D4EhE4y6VBcwYfX/7KJkR5Q9fD9ET6NABAjajUTSNFfCxmNIaQgISggZ3pkgwtTqsA==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + dev: false + + /@octokit/core@4.2.4: + resolution: {integrity: sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==} + engines: {node: '>= 14'} + dependencies: + '@octokit/auth-token': 3.0.2 + '@octokit/graphql': 5.0.4 + '@octokit/request': 6.2.3 + '@octokit/request-error': 3.0.3 + '@octokit/types': 9.3.2 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/core@5.0.0: + resolution: {integrity: sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.0.1 + '@octokit/request': 8.1.0 + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/endpoint@7.0.3: + resolution: {integrity: sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==} + engines: {node: '>= 14'} + dependencies: + '@octokit/types': 8.0.0 + is-plain-object: 5.0.0 + universal-user-agent: 6.0.0 + dev: true + + /@octokit/endpoint@9.0.0: + resolution: {integrity: sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 11.1.0 + is-plain-object: 5.0.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/graphql@5.0.4: + resolution: {integrity: sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==} + engines: {node: '>= 14'} + dependencies: + '@octokit/request': 6.2.3 + '@octokit/types': 8.0.0 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/graphql@7.0.1: + resolution: {integrity: sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request': 8.1.0 + '@octokit/types': 11.1.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/oauth-app@6.0.0: + resolution: {integrity: sha512-bNMkS+vJ6oz2hCyraT9ZfTpAQ8dZNqJJQVNaKjPLx4ue5RZiFdU1YWXguOPR8AaSHS+lKe+lR3abn2siGd+zow==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-app': 7.0.0 + '@octokit/auth-oauth-user': 4.0.0 + '@octokit/auth-unauthenticated': 5.0.0 + '@octokit/core': 5.0.0 + '@octokit/oauth-authorization-url': 6.0.2 + '@octokit/oauth-methods': 4.0.0 + '@types/aws-lambda': 8.10.114 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/oauth-authorization-url@6.0.2: + resolution: {integrity: sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/oauth-methods@4.0.0: + resolution: {integrity: sha512-dqy7BZLfLbi3/8X8xPKUKZclMEK9vN3fK5WF3ortRvtplQTszFvdAGbTo71gGLO+4ZxspNiLjnqdd64Chklf7w==} + engines: {node: '>= 18'} + dependencies: + '@octokit/oauth-authorization-url': 6.0.2 + '@octokit/request': 8.1.0 + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + btoa-lite: 1.0.0 + dev: false + + /@octokit/openapi-types@14.0.0: + resolution: {integrity: sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==} + dev: true + + /@octokit/openapi-types@18.0.0: + resolution: {integrity: sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==} + + /@octokit/plugin-paginate-graphql@4.0.0(@octokit/core@5.0.0): + resolution: {integrity: sha512-7HcYW5tP7/Z6AETAPU14gp5H5KmCPT3hmJrS/5tO7HIgbwenYmgw4OY9Ma54FDySuxMwD+wsJlxtuGWwuZuItA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.0.0 + dev: false + + /@octokit/plugin-paginate-rest@6.1.2(@octokit/core@4.2.4): + resolution: {integrity: sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==} + engines: {node: '>= 14'} + peerDependencies: + '@octokit/core': '>=4' + dependencies: + '@octokit/core': 4.2.4 + '@octokit/tsconfig': 1.0.2 + '@octokit/types': 9.3.2 + dev: true + + /@octokit/plugin-paginate-rest@8.0.0(@octokit/core@5.0.0): + resolution: {integrity: sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.0.0 + '@octokit/types': 11.1.0 + dev: false + + /@octokit/plugin-request-log@1.0.4(@octokit/core@4.2.4): + resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 4.2.4 + dev: true + + /@octokit/plugin-rest-endpoint-methods@7.2.3(@octokit/core@4.2.4): + resolution: {integrity: sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==} + engines: {node: '>= 14'} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 4.2.4 + '@octokit/types': 10.0.0 + dev: true + + /@octokit/plugin-rest-endpoint-methods@9.0.0(@octokit/core@5.0.0): + resolution: {integrity: sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.0.0 + '@octokit/types': 11.1.0 + dev: false + + /@octokit/plugin-retry@6.0.0(@octokit/core@5.0.0): + resolution: {integrity: sha512-a1/A4A+PB1QoAHQfLJxGHhLfSAT03bR1jJz3GgQJZvty2ozawFWs93MiBQXO7SL2YbO7CIq0Goj4qLOBj8JeMQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.0.0 + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + bottleneck: 2.19.5 + dev: false + + /@octokit/plugin-throttling@7.0.0(@octokit/core@5.0.0): + resolution: {integrity: sha512-KL2k/d0uANc8XqP5S64YcNFCudR3F5AaKO39XWdUtlJIjT9Ni79ekWJ6Kj5xvAw87udkOMEPcVf9xEge2+ahew==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^5.0.0 + dependencies: + '@octokit/core': 5.0.0 + '@octokit/types': 11.1.0 + bottleneck: 2.19.5 + dev: false + + /@octokit/request-error@3.0.3: + resolution: {integrity: sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==} + engines: {node: '>= 14'} + dependencies: + '@octokit/types': 9.3.2 + deprecation: 2.3.1 + once: 1.4.0 + dev: true + + /@octokit/request-error@5.0.0: + resolution: {integrity: sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 11.1.0 + deprecation: 2.3.1 + once: 1.4.0 + + /@octokit/request@6.2.3: + resolution: {integrity: sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA==} + engines: {node: '>= 14'} + dependencies: + '@octokit/endpoint': 7.0.3 + '@octokit/request-error': 3.0.3 + '@octokit/types': 9.3.2 + is-plain-object: 5.0.0 + node-fetch: 2.7.0 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/request@8.1.0: + resolution: {integrity: sha512-0gg/NwewU0iXctYBale0VVcCPqOtoW5lsog8cNBJgzV/CyTHa2gicUBOlNnzOk6pJkuwXI34qkq+uRm40PmD4A==} + engines: {node: '>= 18'} + dependencies: + '@octokit/endpoint': 9.0.0 + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + is-plain-object: 5.0.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/rest@19.0.13: + resolution: {integrity: sha512-/EzVox5V9gYGdbAI+ovYj3nXQT1TtTHRT+0eZPcuC05UFSWO3mdO9UY1C0i2eLF9Un1ONJkAk+IEtYGAC+TahA==} + engines: {node: '>= 14'} + dependencies: + '@octokit/core': 4.2.4 + '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.4) + '@octokit/plugin-request-log': 1.0.4(@octokit/core@4.2.4) + '@octokit/plugin-rest-endpoint-methods': 7.2.3(@octokit/core@4.2.4) + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/tsconfig@1.0.2: + resolution: {integrity: sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==} + dev: true + + /@octokit/types@10.0.0: + resolution: {integrity: sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==} + dependencies: + '@octokit/openapi-types': 18.0.0 + dev: true + + /@octokit/types@11.1.0: + resolution: {integrity: sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==} + dependencies: + '@octokit/openapi-types': 18.0.0 + + /@octokit/types@8.0.0: + resolution: {integrity: sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==} + dependencies: + '@octokit/openapi-types': 14.0.0 + dev: true + + /@octokit/types@9.3.2: + resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} + dependencies: + '@octokit/openapi-types': 18.0.0 + dev: true + + /@octokit/webhooks-methods@4.0.0: + resolution: {integrity: sha512-M8mwmTXp+VeolOS/kfRvsDdW+IO0qJ8kYodM/sAysk093q6ApgmBXwK1ZlUvAwXVrp/YVHp6aArj4auAxUAOFw==} + engines: {node: '>= 18'} + dev: false + + /@octokit/webhooks-types@7.1.0: + resolution: {integrity: sha512-y92CpG4kFFtBBjni8LHoV12IegJ+KFxLgKRengrVjKmGE5XMeCuGvlfRe75lTRrgXaG6XIWJlFpIDTlkoJsU8w==} + dev: false + + /@octokit/webhooks@12.0.3: + resolution: {integrity: sha512-8iG+/yza7hwz1RrQ7i7uGpK2/tuItZxZq1aTmeg2TNp2xTUB8F8lZF/FcZvyyAxT8tpDMF74TjFGCDACkf1kAQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request-error': 5.0.0 + '@octokit/webhooks-methods': 4.0.0 + '@octokit/webhooks-types': 7.1.0 + aggregate-error: 3.1.0 + dev: false + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + dev: true + + /@pkgr/utils@2.3.1: + resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + is-glob: 4.0.3 + open: 8.4.0 + picocolors: 1.0.0 + tiny-glob: 0.2.9 + tslib: 2.5.0 + dev: true + + /@pnpm/network.ca-file@1.0.2: + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + dependencies: + graceful-fs: 4.2.10 + dev: true + + /@pnpm/npm-conf@1.0.5: + resolution: {integrity: sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==} + engines: {node: '>=12'} + dependencies: + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + dev: true + + /@release-it/conventional-changelog@7.0.1(release-it@16.1.5): + resolution: {integrity: sha512-qvMSzyaTWVCgbAFJ4OYb2HC2S+h47hqua6A/aYe5surdp9dXu47POz22tJUMyKr4z66g+nh2TElFqaWrLmOC3Q==} + engines: {node: '>=16'} + peerDependencies: + release-it: ^16.0.0 + dependencies: + concat-stream: 2.0.0 + conventional-changelog: 5.1.0 + conventional-recommended-bump: 8.0.0 + release-it: 16.1.5 + semver: 7.5.4 + dev: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sindresorhus/is@5.3.0: + resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} + engines: {node: '>=14.16'} + + /@snyk/github-codeowners@1.1.0: + resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==} + engines: {node: '>=8.10'} + hasBin: true + dependencies: + commander: 4.1.1 + ignore: 5.2.4 + p-map: 4.0.0 + dev: true + + /@szmarczak/http-timer@5.0.1: + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} + dependencies: + defer-to-connect: 2.0.1 + + /@tootallnate/quickjs-emscripten@0.23.0: + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + dev: true + + /@types/aws-lambda@8.10.114: + resolution: {integrity: sha512-M8WpEGfC9iQ6V2Ccq6nGIXoQgeVc6z0Ngk8yCOL5V/TYIxshvb0MWQYLFFTZDesL0zmsoBc4OBjG9DB/4rei6w==} + dev: false + + /@types/btoa-lite@1.0.0: + resolution: {integrity: sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg==} + dev: false + + /@types/chai-subset@1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.5 + dev: true + + /@types/chai@4.3.5: + resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} + dev: true + + /@types/eslint@8.44.2: + resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==} + dependencies: + '@types/estree': 1.0.0 + '@types/json-schema': 7.0.12 + dev: true + + /@types/estree@1.0.0: + resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + dev: true + + /@types/git-url-parse@9.0.1: + resolution: {integrity: sha512-Zf9mY4Mz7N3Nyi341nUkOtgVUQn4j6NS4ndqEha/lOgEbTkHzpD7wZuRagYKzrXNtvawWfsrojoC1nhsQexvNA==} + dev: true + + /@types/http-cache-semantics@4.0.1: + resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} + + /@types/istanbul-lib-coverage@2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: true + + /@types/js-yaml@4.0.5: + resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} + dev: true + + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + dev: true + + /@types/jsonwebtoken@9.0.1: + resolution: {integrity: sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==} + dependencies: + '@types/node': 20.5.6 + dev: false + + /@types/mdast@3.0.10: + resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} + dependencies: + '@types/unist': 2.0.6 + dev: true + + /@types/minimist@1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + + /@types/node@20.5.6: + resolution: {integrity: sha512-Gi5wRGPbbyOTX+4Y2iULQ27oUPrefaB0PxGQJnfyWN3kvEDGM3mIB5M/gQLmitZf7A9FmLeaqxD3L1CXpm3VKQ==} + + /@types/normalize-package-data@2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + + /@types/prettier@2.7.3: + resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + dev: true + + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: true + + /@types/unist@2.0.6: + resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + dev: true + + /@typescript-eslint/eslint-plugin@6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 6.5.0(eslint@8.48.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.5.0 + '@typescript-eslint/type-utils': 6.5.0(eslint@8.48.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.5.0(eslint@8.48.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.5.0 + debug: 4.3.4 + eslint: 8.48.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.5.0(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.5.0 + '@typescript-eslint/types': 6.5.0 + '@typescript-eslint/typescript-estree': 6.5.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.5.0 + debug: 4.3.4 + eslint: 8.48.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@6.5.0: + resolution: {integrity: sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.5.0 + '@typescript-eslint/visitor-keys': 6.5.0 + dev: true + + /@typescript-eslint/scope-manager@6.6.0: + resolution: {integrity: sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/visitor-keys': 6.6.0 + dev: true + + /@typescript-eslint/type-utils@6.5.0(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.5.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.5.0(eslint@8.48.0)(typescript@5.2.2) + debug: 4.3.4 + eslint: 8.48.0 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@6.5.0: + resolution: {integrity: sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/types@6.6.0: + resolution: {integrity: sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/typescript-estree@6.5.0(typescript@5.2.2): + resolution: {integrity: sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.5.0 + '@typescript-eslint/visitor-keys': 6.5.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@6.6.0(typescript@5.2.2): + resolution: {integrity: sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/visitor-keys': 6.6.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@6.5.0(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.5.0 + '@typescript-eslint/types': 6.5.0 + '@typescript-eslint/typescript-estree': 6.5.0(typescript@5.2.2) + eslint: 8.48.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/utils@6.6.0(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.6.0 + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/typescript-estree': 6.6.0(typescript@5.2.2) + eslint: 8.48.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@6.5.0: + resolution: {integrity: sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.5.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@typescript-eslint/visitor-keys@6.6.0: + resolution: {integrity: sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.6.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@vitest/coverage-v8@0.34.3(vitest@0.34.3): + resolution: {integrity: sha512-bNjP0RHe8UxdklCigZlk6FVCNbOiqVjWnpZJ1zKixpvb7YHSaZiN/w+mzpvXIoqyxyePzKC+L+G1oj7SB20PJw==} + peerDependencies: + vitest: '>=0.32.0 <1' + dependencies: + '@ampproject/remapping': 2.2.1 + '@bcoe/v8-coverage': 0.2.3 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + magic-string: 0.30.1 + picocolors: 1.0.0 + std-env: 3.3.3 + test-exclude: 6.0.0 + v8-to-istanbul: 9.1.0 + vitest: 0.34.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@0.34.3: + resolution: {integrity: sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==} + dependencies: + '@vitest/spy': 0.34.3 + '@vitest/utils': 0.34.3 + chai: 4.3.7 + dev: true + + /@vitest/runner@0.34.3: + resolution: {integrity: sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==} + dependencies: + '@vitest/utils': 0.34.3 + p-limit: 4.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@0.34.3: + resolution: {integrity: sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==} + dependencies: + magic-string: 0.30.1 + pathe: 1.1.1 + pretty-format: 29.6.1 + dev: true + + /@vitest/spy@0.34.3: + resolution: {integrity: sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==} + dependencies: + tinyspy: 2.1.1 + dev: true + + /@vitest/utils@0.34.3: + resolution: {integrity: sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==} + dependencies: + diff-sequences: 29.4.3 + loupe: 2.3.6 + pretty-format: 29.6.1 + dev: true + + /JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + /acorn-jsx@5.3.2(acorn@8.10.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.10.0 + dev: true + + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /add-stream@1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + dev: true + + /agent-base@7.1.0: + resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + /ajv-errors@1.0.1(ajv@6.12.6): + resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==} + peerDependencies: + ajv: '>=5.0.0' + dependencies: + ajv: 6.12.6 + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /all-contributors-for-repository@0.1.0: + resolution: {integrity: sha512-rdRo42A0NsoLpKOePBTXmKfbeVmDVU6OJ7fne8KWC3pvkeOg2OE61kOYxnjLXxZ7jn3rercggq8xnUhR6Pc8Aw==} + engines: {node: '>=18'} + dependencies: + conventional-commits-parser: 4.0.0 + octokit: 3.1.0 + dev: false + + /ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + dependencies: + string-width: 4.2.3 + dev: true + + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + + /ansi-escapes@5.0.0: + resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} + engines: {node: '>=12'} + dependencies: + type-fest: 1.4.0 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + dev: true + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + /arity-n@1.0.4: + resolution: {integrity: sha512-fExL2kFDC1Q2DUOx3whE/9KoN66IzkY4b4zUHUBFM1ojEYjZZYDcUW3bek/ufGionX9giIKDC5redH2IlGqcQQ==} + dev: true + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.2 + is-array-buffer: 3.0.2 + dev: true + + /array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + dev: true + + /array-last@1.3.0: + resolution: {integrity: sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==} + engines: {node: '>=0.10.0'} + dependencies: + is-number: 4.0.0 + dev: true + + /array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.map@1.0.5: + resolution: {integrity: sha512-gfaKntvwqYIuC7mLLyv2wzZIJqrRhn5PZ9EfFejSx6a78sV7iDsGpG9P+3oUPtm1Rerqm6nrKS4FYuTIvWfo3g==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + es-array-method-boxes-properly: 1.0.0 + is-string: 1.0.7 + dev: true + + /arraybuffer.prototype.slice@1.0.1: + resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.0 + get-intrinsic: 1.2.1 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + dependencies: + tslib: 2.5.0 + dev: true + + /async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + dependencies: + retry: 0.13.1 + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /babylon@6.18.0: + resolution: {integrity: sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==} + hasBin: true + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /basic-ftp@5.0.3: + resolution: {integrity: sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==} + engines: {node: '>=10.0.0'} + dev: true + + /before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + + /big-integer@1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: true + + /bl@5.1.0: + resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + dependencies: + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: true + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: false + + /bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + dev: false + + /boxen@7.0.1: + resolution: {integrity: sha512-8k2eH6SRAK00NDl1iX5q17RJ8rfl53TajdYxE3ssMLehbg487dEVgsad4pIsZb/QqBgYWIl6JOauMTLGX2Kpkw==} + engines: {node: '>=14.16'} + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.3.0 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + dev: true + + /bplist-parser@0.2.0: + resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} + engines: {node: '>= 5.10.0'} + dependencies: + big-integer: 1.6.51 + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /btoa-lite@1.0.0: + resolution: {integrity: sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==} + dev: false + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + + /builtins@5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.5.4 + dev: true + + /bundle-name@3.0.0: + resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} + engines: {node: '>=12'} + dependencies: + run-applescript: 5.0.0 + dev: true + + /bundle-require@4.0.1(esbuild@0.18.19): + resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + dependencies: + esbuild: 0.18.19 + load-tsconfig: 0.2.5 + dev: true + + /c8@8.0.1: + resolution: {integrity: sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 2.0.0 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.6 + rimraf: 3.0.2 + test-exclude: 6.0.0 + v8-to-istanbul: 9.1.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + dev: true + + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + + /cacheable-request@10.2.10: + resolution: {integrity: sha512-v6WB+Epm/qO4Hdlio/sfUn69r5Shgh39SsE9DSd4bIezP0mblOlObI+I0kUEM7J0JFc+I7pSeMeYaOYtX1N/VQ==} + engines: {node: '>=14.16'} + dependencies: + '@types/http-cache-semantics': 4.0.1 + get-stream: 6.0.1 + http-cache-semantics: 4.1.1 + keyv: 4.5.2 + mimic-response: 4.0.0 + normalize-url: 8.0.0 + responselike: 3.0.0 + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + /camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: true + + /chai@4.3.7: + resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 4.1.3 + get-func-name: 2.0.0 + loupe: 2.3.6 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk-template@1.1.0: + resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==} + engines: {node: '>=14.16'} + dependencies: + chalk: 5.3.0 + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: true + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: true + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: true + + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /check-error@1.0.2: + resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + dev: true + + /cheerio@0.22.0: + resolution: {integrity: sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==} + engines: {node: '>= 0.6'} + dependencies: + css-select: 1.2.0 + dom-serializer: 0.1.1 + entities: 1.1.2 + htmlparser2: 3.10.1 + lodash.assignin: 4.2.0 + lodash.bind: 4.2.1 + lodash.defaults: 4.2.0 + lodash.filter: 4.6.0 + lodash.flatten: 4.4.0 + lodash.foreach: 4.5.0 + lodash.map: 4.6.0 + lodash.merge: 4.6.2 + lodash.pick: 4.4.0 + lodash.reduce: 4.6.0 + lodash.reject: 4.6.0 + lodash.some: 4.6.0 + dev: false + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /ci-info@3.7.1: + resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==} + engines: {node: '>=8'} + dev: true + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + /clear-module@4.1.2: + resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==} + engines: {node: '>=8'} + dependencies: + parent-module: 2.0.0 + resolve-from: 5.0.0 + dev: true + + /cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + dev: true + + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + restore-cursor: 4.0.0 + dev: true + + /cli-spinners@2.9.0: + resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} + engines: {node: '>=6'} + dev: true + + /cli-truncate@3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + requiresBuild: true + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true + + /commander@11.0.0: + resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} + engines: {node: '>=16'} + dev: true + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /comment-json@4.2.3: + resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} + engines: {node: '>= 6'} + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + dev: true + + /comment-parser@1.4.0: + resolution: {integrity: sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw==} + engines: {node: '>= 12.0.0'} + dev: true + + /compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + dev: true + + /compose-function@3.0.3: + resolution: {integrity: sha512-xzhzTJ5eC+gmIzvZq+C3kCJHsp9os6tJkrigDRZclyGtOKINbZtE8n1Tzmeh32jW+BUDPbvZpibwvJHBLGMVwg==} + dependencies: + arity-n: 1.0.4 + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.0 + typedarray: 0.0.6 + dev: true + + /config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: true + + /configstore@6.0.0: + resolution: {integrity: sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==} + engines: {node: '>=12'} + dependencies: + dot-prop: 6.0.1 + graceful-fs: 4.2.10 + unique-string: 3.0.0 + write-file-atomic: 3.0.3 + xdg-basedir: 5.1.0 + dev: true + + /console-fail-test@0.2.3: + resolution: {integrity: sha512-p1xfi9ubVM2X3NpjPH1Gm8Gxc7vXyTwcOIquU8rpUDA+ISSitAOiBDnT/Bti1We5YzZKRxgI0JU+fFvOmOXtvA==} + dev: true + + /conventional-changelog-angular@7.0.0: + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} + dependencies: + compare-func: 2.0.0 + dev: true + + /conventional-changelog-atom@4.0.0: + resolution: {integrity: sha512-q2YtiN7rnT1TGwPTwjjBSIPIzDJCRE+XAUahWxnh+buKK99Kks4WLMHoexw38GXx9OUxAsrp44f9qXe5VEMYhw==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-codemirror@4.0.0: + resolution: {integrity: sha512-hQSojc/5imn1GJK3A75m9hEZZhc3urojA5gMpnar4JHmgLnuM3CUIARPpEk86glEKr3c54Po3WV/vCaO/U8g3Q==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-conventionalcommits@7.0.2: + resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} + engines: {node: '>=16'} + dependencies: + compare-func: 2.0.0 + dev: true + + /conventional-changelog-core@7.0.0: + resolution: {integrity: sha512-UYgaB1F/COt7VFjlYKVE/9tTzfU3VUq47r6iWf6lM5T7TlOxr0thI63ojQueRLIpVbrtHK4Ffw+yQGduw2Bhdg==} + engines: {node: '>=16'} + dependencies: + '@hutson/parse-repository-url': 5.0.0 + add-stream: 1.0.0 + conventional-changelog-writer: 7.0.1 + conventional-commits-parser: 5.0.0 + git-raw-commits: 4.0.0 + git-semver-tags: 7.0.1 + hosted-git-info: 7.0.1 + normalize-package-data: 6.0.0 + read-pkg: 8.1.0 + read-pkg-up: 10.1.0 + dev: true + + /conventional-changelog-ember@4.0.0: + resolution: {integrity: sha512-D0IMhwcJUg1Y8FSry6XAplEJcljkHVlvAZddhhsdbL1rbsqRsMfGx/PIkPYq0ru5aDgn+OxhQ5N5yR7P9mfsvA==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-eslint@5.0.0: + resolution: {integrity: sha512-6JtLWqAQIeJLn/OzUlYmzd9fKeNSWmQVim9kql+v4GrZwLx807kAJl3IJVc3jTYfVKWLxhC3BGUxYiuVEcVjgA==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-express@4.0.0: + resolution: {integrity: sha512-yWyy5c7raP9v7aTvPAWzqrztACNO9+FEI1FSYh7UP7YT1AkWgv5UspUeB5v3Ibv4/o60zj2o9GF2tqKQ99lIsw==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-jquery@5.0.0: + resolution: {integrity: sha512-slLjlXLRNa/icMI3+uGLQbtrgEny3RgITeCxevJB+p05ExiTgHACP5p3XiMKzjBn80n+Rzr83XMYfRInEtCPPw==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-jshint@4.0.0: + resolution: {integrity: sha512-LyXq1bbl0yG0Ai1SbLxIk8ZxUOe3AjnlwE6sVRQmMgetBk+4gY9EO3d00zlEt8Y8gwsITytDnPORl8al7InTjg==} + engines: {node: '>=16'} + dependencies: + compare-func: 2.0.0 + dev: true + + /conventional-changelog-preset-loader@4.1.0: + resolution: {integrity: sha512-HozQjJicZTuRhCRTq4rZbefaiCzRM2pr6u2NL3XhrmQm4RMnDXfESU6JKu/pnKwx5xtdkYfNCsbhN5exhiKGJA==} + engines: {node: '>=16'} + dev: true + + /conventional-changelog-writer@7.0.1: + resolution: {integrity: sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + conventional-commits-filter: 4.0.0 + handlebars: 4.7.8 + json-stringify-safe: 5.0.1 + meow: 12.1.1 + semver: 7.5.4 + split2: 4.2.0 + dev: true + + /conventional-changelog@5.1.0: + resolution: {integrity: sha512-aWyE/P39wGYRPllcCEZDxTVEmhyLzTc9XA6z6rVfkuCD2UBnhV/sgSOKbQrEG5z9mEZJjnopjgQooTKxEg8mAg==} + engines: {node: '>=16'} + dependencies: + conventional-changelog-angular: 7.0.0 + conventional-changelog-atom: 4.0.0 + conventional-changelog-codemirror: 4.0.0 + conventional-changelog-conventionalcommits: 7.0.2 + conventional-changelog-core: 7.0.0 + conventional-changelog-ember: 4.0.0 + conventional-changelog-eslint: 5.0.0 + conventional-changelog-express: 4.0.0 + conventional-changelog-jquery: 5.0.0 + conventional-changelog-jshint: 4.0.0 + conventional-changelog-preset-loader: 4.1.0 + dev: true + + /conventional-commits-filter@4.0.0: + resolution: {integrity: sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==} + engines: {node: '>=16'} + dev: true + + /conventional-commits-parser@3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + JSONStream: 1.3.5 + is-text-path: 1.0.1 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + + /conventional-commits-parser@4.0.0: + resolution: {integrity: sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==} + engines: {node: '>=14'} + hasBin: true + dependencies: + JSONStream: 1.3.5 + is-text-path: 1.0.1 + meow: 8.1.2 + split2: 3.2.2 + dev: false + + /conventional-commits-parser@5.0.0: + resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + JSONStream: 1.3.5 + is-text-path: 2.0.0 + meow: 12.1.1 + split2: 4.2.0 + dev: true + + /conventional-recommended-bump@8.0.0: + resolution: {integrity: sha512-yvGN+VMy00WIe/pJufpmN+I4B2cM/WFK+CFCmDcjyVLyQR6J1KT2iecmA4NQ58gQAiNkvStEjcZp/W9h1JDM1A==} + engines: {node: '>=16'} + hasBin: true + dependencies: + concat-stream: 2.0.0 + conventional-changelog-preset-loader: 4.1.0 + conventional-commits-filter: 4.0.0 + conventional-commits-parser: 5.0.0 + git-raw-commits: 4.0.0 + git-semver-tags: 6.0.0 + meow: 12.1.1 + dev: true + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + + /cosmiconfig@8.0.0: + resolution: {integrity: sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==} + engines: {node: '>=14'} + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + dev: true + + /cosmiconfig@8.2.0: + resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} + engines: {node: '>=14'} + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + dependencies: + type-fest: 1.4.0 + dev: true + + /cspell-dictionary@7.0.1: + resolution: {integrity: sha512-mC2+sjsfxWZ5uYsnUHG/2opnpnoy492o13caai0h4GODV0u3hxhCS4f7twLf0Rdm+Is0MU7wrTecDdDVKu1mOA==} + engines: {node: '>=16'} + dependencies: + '@cspell/cspell-pipe': 7.0.1 + '@cspell/cspell-types': 7.0.1 + cspell-trie-lib: 7.0.1 + fast-equals: 4.0.3 + gensequence: 5.0.2 + dev: true + + /cspell-gitignore@7.0.1: + resolution: {integrity: sha512-ebi4VvH3KqUF9G93EoQA0PUIA8eM/y3GITIVDkdF2Ueo6uIWEeGjSaYNeJgNJHvccBZViR6XsrZuVxBOkSW3Rw==} + engines: {node: '>=16'} + hasBin: true + dependencies: + cspell-glob: 7.0.1 + find-up: 5.0.0 + dev: true + + /cspell-glob@7.0.1: + resolution: {integrity: sha512-Qm2r+FgtwvJnWbW03QoUohTLDkoic1JVjFSbUTua8AlzbOPJ2M+IJZx47rf5dAiUFtxIDsjiaDepcrkyW7q5HQ==} + engines: {node: '>=16'} + dependencies: + micromatch: 4.0.5 + dev: true + + /cspell-grammar@7.0.1: + resolution: {integrity: sha512-qrwll/JWpa2/2cq4a39yLQPn0hsYcPFN8BWr2xsuFuuYjplaUhSU40LbngUAUkbcWGxVrQCR9odClboZ6xzYFQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@cspell/cspell-pipe': 7.0.1 + '@cspell/cspell-types': 7.0.1 + dev: true + + /cspell-io@7.0.1: + resolution: {integrity: sha512-z3dzYFJgredZJYV9piU/rvulCeMixNeJbxBZyHGOGWeKg36iZhXrIkNpK4s6GEAgGB9r/BD9P31E7YQomzhKZA==} + engines: {node: '>=16'} + dependencies: + '@cspell/cspell-service-bus': 7.0.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: true + + /cspell-lib@7.0.1: + resolution: {integrity: sha512-BaFhA0GFnuMEFzEALSt/TgrOl7A6vJSwtqqpdOGI5goLBIu8DDYqIncLrcglELosFo+KXnnYtYtPXuQIX3P5Kw==} + engines: {node: '>=16'} + dependencies: + '@cspell/cspell-bundled-dicts': 7.0.1 + '@cspell/cspell-pipe': 7.0.1 + '@cspell/cspell-resolver': 7.0.1 + '@cspell/cspell-types': 7.0.1 + '@cspell/strong-weak-map': 7.0.1 + clear-module: 4.1.2 + comment-json: 4.2.3 + configstore: 6.0.0 + cosmiconfig: 8.0.0 + cspell-dictionary: 7.0.1 + cspell-glob: 7.0.1 + cspell-grammar: 7.0.1 + cspell-io: 7.0.1 + cspell-trie-lib: 7.0.1 + fast-equals: 5.0.1 + find-up: 6.3.0 + gensequence: 5.0.2 + import-fresh: 3.3.0 + resolve-from: 5.0.0 + vscode-languageserver-textdocument: 1.0.8 + vscode-uri: 3.0.7 + transitivePeerDependencies: + - encoding + dev: true + + /cspell-trie-lib@7.0.1: + resolution: {integrity: sha512-rdY78YK46LUmcez63kMbMF2nCmPIcnWd3a0rivnhyPaVvY+cwNKqpp7WSWOFDLboiMaEdCrdaS4AecspTCLjaw==} + engines: {node: '>=16'} + dependencies: + '@cspell/cspell-pipe': 7.0.1 + '@cspell/cspell-types': 7.0.1 + gensequence: 5.0.2 + dev: true + + /cspell@7.0.1: + resolution: {integrity: sha512-nl35cQJ1XxESRZS5QD6S+X1XtBU9Q/acUPXt8yZjd+PcgkyTwCRk7qwxwEodkTUMP3Yxkg5hGWMtzDXfNK35RQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@cspell/cspell-json-reporter': 7.0.1 + '@cspell/cspell-pipe': 7.0.1 + '@cspell/cspell-types': 7.0.1 + '@cspell/dynamic-import': 7.0.1 + chalk: 5.3.0 + chalk-template: 1.1.0 + commander: 11.0.0 + cspell-gitignore: 7.0.1 + cspell-glob: 7.0.1 + cspell-io: 7.0.1 + cspell-lib: 7.0.1 + fast-glob: 3.3.1 + fast-json-stable-stringify: 2.1.0 + file-entry-cache: 6.0.1 + get-stdin: 9.0.0 + semver: 7.5.4 + strip-ansi: 7.1.0 + vscode-uri: 3.0.7 + transitivePeerDependencies: + - encoding + dev: true + + /css-select@1.2.0: + resolution: {integrity: sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==} + dependencies: + boolbase: 1.0.0 + css-what: 2.1.3 + domutils: 1.5.1 + nth-check: 1.0.2 + dev: false + + /css-what@2.1.3: + resolution: {integrity: sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==} + dev: false + + /dargs@8.1.0: + resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} + engines: {node: '>=12'} + dev: true + + /data-uri-to-buffer@4.0.0: + resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} + engines: {node: '>= 12'} + dev: true + + /data-uri-to-buffer@5.0.1: + resolution: {integrity: sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==} + engines: {node: '>= 14'} + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: true + + /deep-freeze@0.0.1: + resolution: {integrity: sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg==} + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /default-browser-id@3.0.0: + resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} + engines: {node: '>=12'} + dependencies: + bplist-parser: 0.2.0 + untildify: 4.0.0 + dev: true + + /default-browser@4.0.0: + resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} + engines: {node: '>=14.16'} + dependencies: + bundle-name: 3.0.0 + default-browser-id: 3.0.0 + execa: 7.2.0 + titleize: 3.0.0 + dev: true + + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + requiresBuild: true + dependencies: + clone: 1.0.4 + dev: true + + /defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + /define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: true + + /define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dev: true + + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + dev: true + + /deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + + /detect-indent@7.0.1: + resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} + engines: {node: '>=12.20'} + dev: true + + /detect-newline@4.0.0: + resolution: {integrity: sha512-1aXUEPdfGdzVPFpzGJJNgq9o81bGg1s09uxTWsqBlo9PI332uyJRQq13+LK/UN4JfxJbFdCXonUFQ9R/p7yCtw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /diff-sequences@29.4.3: + resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dom-serializer@0.1.1: + resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==} + dependencies: + domelementtype: 1.3.1 + entities: 1.1.2 + dev: false + + /domelementtype@1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + dev: false + + /domhandler@2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + dependencies: + domelementtype: 1.3.1 + dev: false + + /domutils@1.5.1: + resolution: {integrity: sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==} + dependencies: + dom-serializer: 0.1.1 + domelementtype: 1.3.1 + dev: false + + /domutils@1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dependencies: + dom-serializer: 0.1.1 + domelementtype: 1.3.1 + dev: false + + /dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + dependencies: + is-obj: 2.0.0 + dev: true + + /dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + dependencies: + is-obj: 2.0.0 + dev: true + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + + /easy-table@1.2.0: + resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} + dependencies: + ansi-regex: 5.0.1 + optionalDependencies: + wcwidth: 1.0.1 + dev: true + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /emoji-regex@10.2.1: + resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /entities@1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + + /entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + + /es-abstract@1.22.1: + resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.1 + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.1 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + safe-array-concat: 1.0.0 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.11 + dev: true + + /es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + dev: true + + /es-get-iterator@1.1.2: + resolution: {integrity: sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /esbuild@0.17.18: + resolution: {integrity: sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.18 + '@esbuild/android-arm64': 0.17.18 + '@esbuild/android-x64': 0.17.18 + '@esbuild/darwin-arm64': 0.17.18 + '@esbuild/darwin-x64': 0.17.18 + '@esbuild/freebsd-arm64': 0.17.18 + '@esbuild/freebsd-x64': 0.17.18 + '@esbuild/linux-arm': 0.17.18 + '@esbuild/linux-arm64': 0.17.18 + '@esbuild/linux-ia32': 0.17.18 + '@esbuild/linux-loong64': 0.17.18 + '@esbuild/linux-mips64el': 0.17.18 + '@esbuild/linux-ppc64': 0.17.18 + '@esbuild/linux-riscv64': 0.17.18 + '@esbuild/linux-s390x': 0.17.18 + '@esbuild/linux-x64': 0.17.18 + '@esbuild/netbsd-x64': 0.17.18 + '@esbuild/openbsd-x64': 0.17.18 + '@esbuild/sunos-x64': 0.17.18 + '@esbuild/win32-arm64': 0.17.18 + '@esbuild/win32-ia32': 0.17.18 + '@esbuild/win32-x64': 0.17.18 + dev: true + + /esbuild@0.18.19: + resolution: {integrity: sha512-ra3CaIKCzJp5bU5BDfrCc0FRqKj71fQi+gbld0aj6lN0ifuX2fWJYPgLVLGwPfA+ruKna+OWwOvf/yHj6n+i0g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.19 + '@esbuild/android-arm64': 0.18.19 + '@esbuild/android-x64': 0.18.19 + '@esbuild/darwin-arm64': 0.18.19 + '@esbuild/darwin-x64': 0.18.19 + '@esbuild/freebsd-arm64': 0.18.19 + '@esbuild/freebsd-x64': 0.18.19 + '@esbuild/linux-arm': 0.18.19 + '@esbuild/linux-arm64': 0.18.19 + '@esbuild/linux-ia32': 0.18.19 + '@esbuild/linux-loong64': 0.18.19 + '@esbuild/linux-mips64el': 0.18.19 + '@esbuild/linux-ppc64': 0.18.19 + '@esbuild/linux-riscv64': 0.18.19 + '@esbuild/linux-s390x': 0.18.19 + '@esbuild/linux-x64': 0.18.19 + '@esbuild/netbsd-x64': 0.18.19 + '@esbuild/openbsd-x64': 0.18.19 + '@esbuild/sunos-x64': 0.18.19 + '@esbuild/win32-arm64': 0.18.19 + '@esbuild/win32-ia32': 0.18.19 + '@esbuild/win32-x64': 0.18.19 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + + /escape-goat@4.0.0: + resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} + engines: {node: '>=12'} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + + /eslint-plugin-deprecation@2.0.0(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-OAm9Ohzbj11/ZFyICyR5N6LbOIvQMp7ZU2zI7Ej0jIc8kiGUERXPNMfw2QqqHD1ZHtjMub3yPZILovYEYucgoQ==} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: ^4.2.4 || ^5.0.0 + dependencies: + '@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.2.2) + eslint: 8.48.0 + tslib: 2.5.0 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-es-x@7.2.0(eslint@8.48.0): + resolution: {integrity: sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + '@eslint-community/regexpp': 4.6.2 + eslint: 8.48.0 + dev: true + + /eslint-plugin-eslint-comments@3.2.0(eslint@8.48.0): + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + escape-string-regexp: 1.0.5 + eslint: 8.48.0 + ignore: 5.2.4 + dev: true + + /eslint-plugin-jsdoc@46.5.0(eslint@8.48.0): + resolution: {integrity: sha512-aulXdA4I1dyWpzyS1Nh/GNoS6PavzeucxEapnMR4JUERowWvaEk2Y4A5irpHAcdXtBBHLVe8WIhdXNjoAlGQgA==} + engines: {node: '>=16'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@es-joy/jsdoccomment': 0.40.1 + are-docs-informative: 0.0.2 + comment-parser: 1.4.0 + debug: 4.3.4 + escape-string-regexp: 4.0.0 + eslint: 8.48.0 + esquery: 1.5.0 + is-builtin-module: 3.2.1 + semver: 7.5.4 + spdx-expression-parse: 3.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-jsonc@2.9.0(eslint@8.48.0): + resolution: {integrity: sha512-RK+LeONVukbLwT2+t7/OY54NJRccTXh/QbnXzPuTLpFMVZhPuq1C9E07+qWenGx7rrQl0kAalAWl7EmB+RjpGA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + eslint: 8.48.0 + jsonc-eslint-parser: 2.3.0 + natural-compare: 1.4.0 + dev: true + + /eslint-plugin-markdown@3.0.1(eslint@8.48.0): + resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + eslint: 8.48.0 + mdast-util-from-markdown: 0.8.5 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-n@16.0.2(eslint@8.48.0): + resolution: {integrity: sha512-Y66uDfUNbBzypsr0kELWrIz+5skicECrLUqlWuXawNSLUq3ltGlCwu6phboYYOTSnoTdHgTLrc+5Ydo6KjzZog==} + engines: {node: '>=16.0.0'} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + builtins: 5.0.1 + eslint: 8.48.0 + eslint-plugin-es-x: 7.2.0(eslint@8.48.0) + ignore: 5.2.4 + is-core-module: 2.13.0 + minimatch: 3.1.2 + resolve: 1.22.2 + semver: 7.5.4 + dev: true + + /eslint-plugin-no-only-tests@3.1.0: + resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==} + engines: {node: '>=5.0.0'} + dev: true + + /eslint-plugin-perfectionist@2.0.1(eslint@8.48.0)(typescript@5.2.2): + resolution: {integrity: sha512-fesAa1NzOClxmIA1/3bSIy/8nZY5el+L6WDzhJh4yxjgWGqkwsTva4Z5BhMFgALI7KFI1EO8tWyY8apt4f/3GA==} + peerDependencies: + astro-eslint-parser: ^0.14.0 + eslint: '>=8.0.0' + svelte: '>=3.0.0' + svelte-eslint-parser: ^0.32.0 + vue-eslint-parser: '>=9.0.0' + peerDependenciesMeta: + astro-eslint-parser: + optional: true + svelte: + optional: true + svelte-eslint-parser: + optional: true + vue-eslint-parser: + optional: true + dependencies: + '@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.2.2) + eslint: 8.48.0 + minimatch: 9.0.3 + natural-compare-lite: 1.4.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /eslint-plugin-regexp@1.15.0(eslint@8.48.0): + resolution: {integrity: sha512-YEtQPfdudafU7RBIFci81R/Q1yErm0mVh3BkGnXD2Dk8DLwTFdc2ITYH1wCnHKim2gnHfPFgrkh+b2ozyyU7ag==} + engines: {node: ^12 || >=14} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + '@eslint-community/regexpp': 4.6.2 + comment-parser: 1.4.0 + eslint: 8.48.0 + grapheme-splitter: 1.0.4 + jsdoctypeparser: 9.0.0 + refa: 0.11.0 + regexp-ast-analysis: 0.6.0 + scslre: 0.2.0 + dev: true + + /eslint-plugin-vitest@0.3.1(@typescript-eslint/eslint-plugin@6.5.0)(eslint@8.48.0)(vitest@0.34.3): + resolution: {integrity: sha512-GeR3zISHmqUGWK2sfW+eyCZivMqiQYzPf9UttHXBiEyMveS/jkKLHCrHUllwr3Hz1+i0zoseANd2xL0cFha8Eg==} + engines: {node: 14.x || >= 16} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: '>=8.0.0' + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.2.2) + eslint: 8.48.0 + typescript: 5.2.2 + vitest: 0.34.3 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-yml@1.8.0(eslint@8.48.0): + resolution: {integrity: sha512-fgBiJvXD0P2IN7SARDJ2J7mx8t0bLdG6Zcig4ufOqW5hOvSiFxeUyc2g5I1uIm8AExbo26NNYCcTGZT0MXTsyg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.4 + eslint: 8.48.0 + lodash: 4.17.21 + natural-compare: 1.4.0 + yaml-eslint-parser: 1.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.48.0: + resolution: {integrity: sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.48.0) + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.48.0 + '@humanwhocodes/config-array': 0.11.10 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + acorn-jsx: 5.3.2(acorn@8.10.0) + eslint-visitor-keys: 3.4.3 + dev: true + + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + dev: true + + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: false + + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-equals@4.0.3: + resolution: {integrity: sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==} + dev: true + + /fast-equals@5.0.1: + resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} + engines: {node: '>=6.0.0'} + dev: true + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: true + + /figures@5.0.0: + resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} + engines: {node: '>=14'} + dependencies: + escape-string-regexp: 5.0.0 + is-unicode-supported: 1.3.0 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /filter-iterator@0.0.1: + resolution: {integrity: sha512-v4lhL7Qa8XpbW3LN46CEnmhGk3eHZwxfNl5at20aEkreesht4YKb/Ba3BUIbnPhAC/r3dmu7ABaGk6MAvh2alA==} + dev: true + + /filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + dev: true + + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + dev: true + + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /foreground-child@2.0.0: + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} + engines: {node: '>=8.0.0'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 3.0.7 + dev: true + + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + + /form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /gensequence@5.0.2: + resolution: {integrity: sha512-JlKEZnFc6neaeSVlkzBGGgkIoIaSxMgvdamRoPN8r3ozm2r9dusqxeKqYQ7lhzmj2UhFQP8nkyfCaiLQxiLrDA==} + engines: {node: '>=14'} + dev: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + /get-func-name@2.0.0: + resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + dev: true + + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 + dev: true + + /get-stdin@9.0.0: + resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} + engines: {node: '>=12'} + dev: true + + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: false + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + + /get-tsconfig@4.5.0: + resolution: {integrity: sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==} + dev: true + + /get-uri@6.0.1: + resolution: {integrity: sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==} + engines: {node: '>= 14'} + dependencies: + basic-ftp: 5.0.3 + data-uri-to-buffer: 5.0.1 + debug: 4.3.4 + fs-extra: 8.1.0 + transitivePeerDependencies: + - supports-color + dev: true + + /git-hooks-list@3.0.0: + resolution: {integrity: sha512-XDfdemBGJIMAsHHOONHQxEH5dX2kCpE6MGZ1IsNvBuDPBZM3p4EAwAC7ygMjn/1/x+BJX0TK1ara1Zrh7JCFdQ==} + dev: true + + /git-raw-commits@4.0.0: + resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + dev: true + + /git-remote-origin-url@4.0.0: + resolution: {integrity: sha512-EAxDksNdjuWgmVW9pVvA9jQDi/dmTaiDONktIy7qiRRhBZUI4FQK1YvBvteuTSX24aNKg9lfgxNYJEeeSXe6DA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + gitconfiglocal: 2.1.0 + dev: false + + /git-semver-tags@6.0.0: + resolution: {integrity: sha512-v5BL6psuUy+Ftuo99141XlOIDoJtKw5+YyDANS7fknSP0iT4cVIanc3toDsH4K+VpIWc19l2/xkwQmXMfloeUA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + meow: 12.1.1 + semver: 7.5.4 + dev: true + + /git-semver-tags@7.0.1: + resolution: {integrity: sha512-NY0ZHjJzyyNXHTDZmj+GG7PyuAKtMsyWSwh07CR2hOZFa+/yoTsXci/nF2obzL8UDhakFNkD9gNdt/Ed+cxh2Q==} + engines: {node: '>=16'} + hasBin: true + dependencies: + meow: 12.1.1 + semver: 7.5.4 + dev: true + + /git-up@7.0.0: + resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + dependencies: + is-ssh: 1.4.0 + parse-url: 8.1.0 + + /git-url-parse@13.1.0: + resolution: {integrity: sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==} + dependencies: + git-up: 7.0.0 + + /gitconfiglocal@2.1.0: + resolution: {integrity: sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==} + dependencies: + ini: 1.3.8 + dev: false + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@10.3.4: + resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.2.0 + minimatch: 9.0.3 + minipass: 5.0.0 + path-scurry: 1.10.1 + dev: true + + /glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + + /global-dirs@3.0.1: + resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} + engines: {node: '>=10'} + dependencies: + ini: 2.0.0 + dev: true + + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true + + /globals@13.19.0: + resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 + dev: true + + /globalyzer@0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /got@12.6.0: + resolution: {integrity: sha512-WTcaQ963xV97MN3x0/CbAriXFZcXCfgxVp91I+Ze6pawQOa7SgzwSx2zIJJsX+kTajMnVs0xcFD1TxZKFqhdnQ==} + engines: {node: '>=14.16'} + dependencies: + '@sindresorhus/is': 5.3.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.10 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.0 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + + /got@13.0.0: + resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} + engines: {node: '>=16'} + dependencies: + '@sindresorhus/is': 5.3.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.10 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.0 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + dev: true + + /graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: true + + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + dev: true + + /has-own-property@0.1.0: + resolution: {integrity: sha512-14qdBKoonU99XDhWcFKZTShK+QV47qU97u8zzoVo9cL5TZ3BmBHXogItSt9qJjR0KUMFRhcCW8uGIGl8nkl7Aw==} + dev: true + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.1 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has-yarn@3.0.0: + resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + + /hosted-git-info@7.0.1: + resolution: {integrity: sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==} + engines: {node: ^16.14.0 || >=18.0.0} + dependencies: + lru-cache: 10.0.1 + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /htmlparser2@3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: false + + /http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + /http-proxy-agent@7.0.0: + resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /http2-wrapper@2.2.0: + resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} + engines: {node: '>=10.19.0'} + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + /https-proxy-agent@7.0.0: + resolution: {integrity: sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: false + + /husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /identity-function@1.0.0: + resolution: {integrity: sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw==} + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + dev: true + + /import-meta-resolve@3.0.0: + resolution: {integrity: sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==} + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + /ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + dev: true + + /ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /inquirer@9.2.10: + resolution: {integrity: sha512-tVVNFIXU8qNHoULiazz612GFl+yqNfjMTbLuViNJE/d860Qxrd3NMrse8dm40VUQLOQeULvaQF8lpAhvysjeyA==} + engines: {node: '>=14.18.0'} + dependencies: + '@ljharb/through': 2.3.9 + ansi-escapes: 4.3.2 + chalk: 5.3.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 5.0.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /internal-slot@1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + dev: true + + /ip@1.1.8: + resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} + dev: true + + /ip@2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + dev: true + + /irregular-plurals@3.3.0: + resolution: {integrity: sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==} + engines: {node: '>=8'} + dev: true + + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: true + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: true + + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.10 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + dependencies: + ci-info: 3.7.1 + dev: true + + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: true + + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: true + + /is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: true + + /is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + dependencies: + is-docker: 3.0.0 + dev: true + + /is-installed-globally@0.4.0: + resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} + engines: {node: '>=10'} + dependencies: + global-dirs: 3.0.1 + is-path-inside: 3.0.3 + dev: true + + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + + /is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + dev: true + + /is-iterable@1.1.1: + resolution: {integrity: sha512-EdOZCr0NsGE00Pot+x1ZFx9MJK3C6wy91geZpXwvwexDLJvA4nzYyZf7r+EIwSeVsOLDdBz7ATg9NqKTzuNYuQ==} + engines: {node: '>= 4'} + dev: true + + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-npm@6.0.0: + resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number@4.0.0: + resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + /is-plain-obj@3.0.0: + resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} + engines: {node: '>=10'} + dev: true + + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: true + + /is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-ssh@1.4.0: + resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + dependencies: + protocols: 2.0.1 + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-text-path@1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} + dependencies: + text-extensions: 1.9.0 + + /is-text-path@2.0.0: + resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} + engines: {node: '>=8'} + dependencies: + text-extensions: 2.4.0 + dev: true + + /is-typed-array@1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: true + + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + dev: true + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: true + + /is-yarn-global@0.4.1: + resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==} + engines: {node: '>=12'} + dev: true + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /issue-parser@6.0.0: + resolution: {integrity: sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==} + engines: {node: '>=10.13'} + dependencies: + lodash.capitalize: 4.2.1 + lodash.escaperegexp: 4.1.2 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.uniqby: 4.7.0 + dev: true + + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /iterable-lookahead@1.0.0: + resolution: {integrity: sha512-hJnEP2Xk4+44DDwJqUQGdXal5VbyeWLaPyDl2AQc242Zr7iqz4DgpQOrEzglWVMGHMDCkguLHEKxd1+rOsmgSQ==} + engines: {node: '>=4'} + dev: true + + /iterate-iterator@1.0.2: + resolution: {integrity: sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==} + dev: true + + /iterate-value@1.0.2: + resolution: {integrity: sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==} + dependencies: + es-get-iterator: 1.1.2 + iterate-iterator: 1.0.2 + dev: true + + /jackspeak@2.2.0: + resolution: {integrity: sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + + /jiti@1.19.3: + resolution: {integrity: sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==} + hasBin: true + dev: true + + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + + /jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + dev: true + + /jsdoctypeparser@9.0.0: + resolution: {integrity: sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==} + engines: {node: '>=10'} + hasBin: true + dev: true + + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + /json-parse-even-better-errors@3.0.0: + resolution: {integrity: sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + + /jsonc-eslint-parser@2.3.0: + resolution: {integrity: sha512-9xZPKVYp9DxnM3sd1yAsh/d59iIaswDkai8oTxbursfKYbg/ibjX0IzFt35+VZ8iEW453TVTXztnRvYUQlAfUQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.5.4 + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + /jsonwebtoken@9.0.0: + resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash: 4.17.21 + ms: 2.1.3 + semver: 7.5.4 + dev: false + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + + /keyv@4.5.2: + resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} + dependencies: + json-buffer: 3.0.1 + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + /knip@2.29.0: + resolution: {integrity: sha512-gA2M5JvH+HroslEiKdDzic7TYaMmysX766VdaqK4JoWc/FVkFESxs/qHcbIYD19uBkcVl26X22GR81lWvICb7w==} + engines: {node: '>=16.17.0 <17 || >=18.6.0'} + hasBin: true + dependencies: + '@ericcornelissen/bash-parser': 0.5.2 + '@npmcli/map-workspaces': 3.0.4 + '@snyk/github-codeowners': 1.1.0 + chalk: 5.3.0 + easy-table: 1.2.0 + fast-glob: 3.3.1 + globby: 13.2.2 + jiti: 1.19.3 + js-yaml: 4.1.0 + micromatch: 4.0.5 + minimist: 1.2.8 + pretty-ms: 8.0.0 + strip-json-comments: 5.0.0 + summary: 2.1.0 + typescript: 5.2.2 + zod: 3.22.2 + zod-validation-error: 1.5.0(zod@3.22.2) + dev: true + + /latest-version@7.0.0: + resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} + engines: {node: '>=14.16'} + dependencies: + package-json: 8.1.0 + dev: true + + /lazy-value@3.0.0: + resolution: {integrity: sha512-BBcLu68yjVhYSqRLYbiDOCWOD7Q8pm39SNL+UfhSfroJScJKRZMaoDJMXLYA2wBA+JCY/5ICoVEvG3yQjqQtKw==} + engines: {node: '>=12'} + dev: false + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: true + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + /lines-and-columns@2.0.3: + resolution: {integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /linkify-it@2.2.0: + resolution: {integrity: sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==} + dependencies: + uc.micro: 1.0.6 + dev: true + + /linkify-it@4.0.1: + resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} + dependencies: + uc.micro: 1.0.6 + dev: true + + /lint-staged@14.0.1: + resolution: {integrity: sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==} + engines: {node: ^16.14.0 || >=18.0.0} + hasBin: true + dependencies: + chalk: 5.3.0 + commander: 11.0.0 + debug: 4.3.4 + execa: 7.2.0 + lilconfig: 2.1.0 + listr2: 6.6.1 + micromatch: 4.0.5 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.3.1 + transitivePeerDependencies: + - enquirer + - supports-color + dev: true + + /listr2@6.6.1: + resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} + engines: {node: '>=16.0.0'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 5.0.1 + rfdc: 1.3.0 + wrap-ansi: 8.1.0 + dev: true + + /load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + dev: true + + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-locate: 6.0.0 + dev: true + + /lodash.assignin@4.2.0: + resolution: {integrity: sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==} + dev: false + + /lodash.bind@4.2.1: + resolution: {integrity: sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==} + dev: false + + /lodash.capitalize@4.2.1: + resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} + dev: true + + /lodash.curry@4.1.1: + resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==} + dev: true + + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + + /lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + dev: true + + /lodash.filter@4.6.0: + resolution: {integrity: sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==} + dev: false + + /lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + dev: false + + /lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: true + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: true + + /lodash.map@4.6.0: + resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} + dev: false + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + /lodash.pick@4.4.0: + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + dev: false + + /lodash.reduce@4.6.0: + resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==} + dev: false + + /lodash.reject@4.6.0: + resolution: {integrity: sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==} + dev: false + + /lodash.some@4.6.0: + resolution: {integrity: sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==} + dev: false + + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true + + /lodash.uniqby@4.7.0: + resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /log-symbols@5.1.0: + resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} + engines: {node: '>=12'} + dependencies: + chalk: 5.3.0 + is-unicode-supported: 1.3.0 + dev: true + + /log-update@5.0.1: + resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + ansi-escapes: 5.0.0 + cli-cursor: 4.0.0 + slice-ansi: 5.0.0 + strip-ansi: 7.1.0 + wrap-ansi: 8.1.0 + dev: true + + /loupe@2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + dependencies: + get-func-name: 2.0.0 + dev: true + + /lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + /lru-cache@10.0.1: + resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} + engines: {node: 14 || >=16.14} + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: true + + /macos-release@3.1.0: + resolution: {integrity: sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /magic-string@0.16.0: + resolution: {integrity: sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==} + dependencies: + vlq: 0.2.3 + dev: true + + /magic-string@0.30.1: + resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + /map-obj@2.0.0: + resolution: {integrity: sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==} + engines: {node: '>=4'} + dev: true + + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + + /markdown-it@13.0.1: + resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 3.0.1 + linkify-it: 4.0.1 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: true + + /markdown-it@8.4.2: + resolution: {integrity: sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==} + hasBin: true + dependencies: + argparse: 1.0.10 + entities: 1.1.2 + linkify-it: 2.2.0 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: true + + /markdownlint-cli@0.37.0: + resolution: {integrity: sha512-hNKAc0bWBBuVhJbSWbUhRzavstiB4o1jh3JeSpwC4/dt6eJ54lRfYHRxVdzVp4qGWBKbeE6Pg490PFEfrKjqSg==} + engines: {node: '>=16'} + hasBin: true + dependencies: + commander: 11.0.0 + get-stdin: 9.0.0 + glob: 10.3.4 + ignore: 5.2.4 + js-yaml: 4.1.0 + jsonc-parser: 3.2.0 + markdownlint: 0.31.1 + minimatch: 9.0.3 + run-con: 1.3.2 + dev: true + + /markdownlint-micromark@0.1.7: + resolution: {integrity: sha512-BbRPTC72fl5vlSKv37v/xIENSRDYL/7X/XoFzZ740FGEbs9vZerLrIkFRY0rv7slQKxDczToYuMmqQFN61fi4Q==} + engines: {node: '>=16'} + dev: true + + /markdownlint@0.11.0: + resolution: {integrity: sha512-wE5WdKD6zW2DQaPQ5TFBTXh5j76DnWd/IFffnDQgHmi6Y61DJXBDfLftZ/suJHuv6cwPjM6gKw2GaRLJMOR+Mg==} + engines: {node: '>=6'} + dependencies: + markdown-it: 8.4.2 + dev: true + + /markdownlint@0.31.1: + resolution: {integrity: sha512-CKMR2hgcIBrYlIUccDCOvi966PZ0kJExDrUi1R+oF9PvqQmCrTqjOsgIvf2403OmJ+CWomuzDoylr6KbuMyvHA==} + engines: {node: '>=16'} + dependencies: + markdown-it: 13.0.1 + markdownlint-micromark: 0.1.7 + dev: true + + /mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-string: 2.0.0 + micromark: 2.11.4 + parse-entities: 2.0.0 + unist-util-stringify-position: 2.0.3 + transitivePeerDependencies: + - supports-color + dev: true + + /mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + dev: true + + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + dev: true + + /meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + dev: true + + /meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + + /meow@9.0.0: + resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize: 1.2.0 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + dependencies: + debug: 4.3.4 + parse-entities: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + /mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: true + + /mlly@1.4.0: + resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} + dependencies: + acorn: 8.10.0 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.1.2 + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: true + + /netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + dev: true + + /new-github-release-url@2.0.0: + resolution: {integrity: sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + type-fest: 2.19.0 + dev: true + + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.0 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.2 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + + /normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.13.0 + semver: 7.5.4 + validate-npm-package-license: 3.0.4 + + /normalize-package-data@6.0.0: + resolution: {integrity: sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==} + engines: {node: ^16.14.0 || >=18.0.0} + dependencies: + hosted-git-info: 7.0.1 + is-core-module: 2.13.0 + semver: 7.5.4 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /normalize-url@8.0.0: + resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} + engines: {node: '>=14.16'} + + /npm-email@4.0.1: + resolution: {integrity: sha512-pksm4gQZQbjZgOvVDn2CNB270mnvYM/BsR/UylL3yHcV3ieuUIqBi/QSMsVdyAsVgmTuwkhjxciv+xUpTDRPqg==} + engines: {node: ^14.17.0 || >=16.0.0} + dependencies: + got: 12.6.0 + dev: false + + /npm-normalize-package-bin@3.0.0: + resolution: {integrity: sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /npm-package-json-lint-config-default@6.0.0(npm-package-json-lint@7.0.0): + resolution: {integrity: sha512-Ek3EcbMEjvGSxEywxNF2tTHuzvtaUH48Z7sljmeSejANMSpE1ssIN0oOG/Tlpc+U/P7ElfsOySIjijjmEnvMGw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + npm-package-json-lint: ^7.0.0 + dependencies: + npm-package-json-lint: 7.0.0 + dev: true + + /npm-package-json-lint@7.0.0: + resolution: {integrity: sha512-Yn8flnPx/7hTxwejWL5urm8sbEahq8ic3R80d7nlBvS6C58JEmJpUqvO7Ksy8izRzpbrHq0Anwlv/nQg5OYf8Q==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + ajv: 6.12.6 + ajv-errors: 1.0.1(ajv@6.12.6) + chalk: 4.1.2 + cosmiconfig: 8.2.0 + debug: 4.3.4 + globby: 11.1.0 + ignore: 5.2.4 + is-plain-obj: 3.0.0 + jsonc-parser: 3.2.0 + log-symbols: 4.1.0 + meow: 9.0.0 + plur: 4.0.0 + semver: 7.5.4 + slash: 3.0.0 + strip-json-comments: 3.1.1 + type-fest: 3.12.0 + validate-npm-package-name: 5.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + + /npm-user@5.0.1: + resolution: {integrity: sha512-XD7j7N1R3m/R+I5J2NRGd4OZaJ8IdDwnz4zIDvUQSWh7ZsXfFNCAZdJ1ETA+4wdFobVfWni1SChGczt8ee0AJA==} + engines: {node: '>=14.16'} + dependencies: + cheerio: 0.22.0 + got: 12.6.0 + npm-email: 4.0.1 + dev: false + + /nth-check@1.0.2: + resolution: {integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==} + dependencies: + boolbase: 1.0.0 + dev: false + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object-pairs@0.1.0: + resolution: {integrity: sha512-3ECr6K831I4xX/Mduxr9UC+HPOz/d6WKKYj9p4cmC8Lg8p7g8gitzsxNX5IWlSIgFWN/a4JgrJaoAMKn20oKwA==} + dev: true + + /object-values@1.0.0: + resolution: {integrity: sha512-+8hwcz/JnQ9EpLIXzN0Rs7DLsBpJNT/xYehtB/jU93tHYr5BFEO8E+JGQNOSqE7opVzz5cGksKFHt7uUJVLSjQ==} + engines: {node: '>=0.10.0'} + dev: true + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /octokit@3.1.0: + resolution: {integrity: sha512-dmIH5D+edpb4/ASd6ZGo6BiRR1g4ytu8lG4f+6XN/2AW+CSuTsT0nj1d6rv/HKgoflMQ1+rb3KlVWcvrmgQZhw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/app': 14.0.0 + '@octokit/core': 5.0.0 + '@octokit/oauth-app': 6.0.0 + '@octokit/plugin-paginate-graphql': 4.0.0(@octokit/core@5.0.0) + '@octokit/plugin-paginate-rest': 8.0.0(@octokit/core@5.0.0) + '@octokit/plugin-rest-endpoint-methods': 9.0.0(@octokit/core@5.0.0) + '@octokit/plugin-retry': 6.0.0(@octokit/core@5.0.0) + '@octokit/plugin-throttling': 7.0.0(@octokit/core@5.0.0) + '@octokit/request-error': 5.0.0 + '@octokit/types': 11.1.0 + dev: false + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + + /open@8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + + /open@9.1.0: + resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} + engines: {node: '>=14.16'} + dependencies: + default-browser: 4.0.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 2.2.0 + dev: true + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.0 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + + /ora@7.0.1: + resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} + engines: {node: '>=16'} + dependencies: + chalk: 5.3.0 + cli-cursor: 4.0.0 + cli-spinners: 2.9.0 + is-interactive: 2.0.0 + is-unicode-supported: 1.3.0 + log-symbols: 5.1.0 + stdin-discarder: 0.1.0 + string-width: 6.1.0 + strip-ansi: 7.1.0 + dev: true + + /os-name@5.1.0: + resolution: {integrity: sha512-YEIoAnM6zFmzw3PQ201gCVCIWbXNyKObGlVvpAVvraAeOHnlYVKFssbA/riRX5R40WA6kKrZ7Dr7dWzO3nKSeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + macos-release: 3.1.0 + windows-release: 5.0.1 + dev: true + + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} + + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-limit: 4.0.0 + dev: true + + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: true + + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + /pac-proxy-agent@7.0.0: + resolution: {integrity: sha512-t4tRAMx0uphnZrio0S0Jw9zg3oDbz1zVhQ/Vy18FjLfP1XOLNUEjaVxYCYRI6NS+BsMBXKIzV6cTLOkO9AtywA==} + engines: {node: '>= 14'} + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.0 + debug: 4.3.4 + get-uri: 6.0.1 + http-proxy-agent: 7.0.0 + https-proxy-agent: 7.0.0 + pac-resolver: 7.0.0 + socks-proxy-agent: 8.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /pac-resolver@7.0.0: + resolution: {integrity: sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==} + engines: {node: '>= 14'} + dependencies: + degenerator: 5.0.1 + ip: 1.1.8 + netmask: 2.0.2 + dev: true + + /package-json@8.1.0: + resolution: {integrity: sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==} + engines: {node: '>=14.16'} + dependencies: + got: 12.6.0 + registry-auth-token: 5.0.1 + registry-url: 6.0.1 + semver: 7.5.4 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parent-module@2.0.0: + resolution: {integrity: sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==} + engines: {node: '>=8'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: true + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.22.5 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + /parse-json@7.1.0: + resolution: {integrity: sha512-ihtdrgbqdONYD156Ap6qTcaGcGdkdAxodO1wLqQ/j7HP1u2sFYppINiq4jyC8F+Nm+4fVufylCV00QmkTHkSUg==} + engines: {node: '>=16'} + dependencies: + '@babel/code-frame': 7.22.5 + error-ex: 1.3.2 + json-parse-even-better-errors: 3.0.0 + lines-and-columns: 2.0.3 + type-fest: 3.12.0 + dev: true + + /parse-ms@3.0.0: + resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} + engines: {node: '>=12'} + dev: true + + /parse-path@7.0.0: + resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} + dependencies: + protocols: 2.0.1 + + /parse-url@8.1.0: + resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + dependencies: + parse-path: 7.0.0 + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + /path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.0.1 + minipass: 5.0.0 + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.0 + pathe: 1.1.1 + dev: true + + /plur@4.0.0: + resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==} + engines: {node: '>=10'} + dependencies: + irregular-plurals: 3.3.0 + dev: true + + /postcss-load-config@4.0.1: + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + yaml: 2.3.1 + dev: true + + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-plugin-curly@0.1.2(prettier@3.0.2): + resolution: {integrity: sha512-DNc9cd7SI/p9hJ2/HanlRgdb5s2nFO9ZZXXe/HsdPFSF7fbs7zYKsh0lEncuWMhGTBePQbVJjv94jEcNXhlaTw==} + engines: {node: '>=18'} + peerDependencies: + prettier: ^2 || ^3 + dependencies: + '@babel/parser': 7.22.5 + '@babel/traverse': 7.22.5 + prettier: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /prettier-plugin-packagejson@2.4.5(prettier@3.0.2): + resolution: {integrity: sha512-glG71jE1gO3y5+JNAhC8X+4yrlN28rub6Aj461SKbaPie9RgMiHKcInH2Moi2VGOfkTXaEHBhg4uVMBqa+kBUA==} + peerDependencies: + prettier: '>= 1.16.0' + peerDependenciesMeta: + prettier: + optional: true + dependencies: + prettier: 3.0.2 + sort-package-json: 2.5.1 + synckit: 0.8.5 + dev: true + + /prettier@3.0.2: + resolution: {integrity: sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==} + engines: {node: '>=14'} + hasBin: true + + /pretty-format@29.6.1: + resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.0 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /pretty-ms@8.0.0: + resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} + engines: {node: '>=14.16'} + dependencies: + parse-ms: 3.0.0 + dev: true + + /promise.allsettled@1.0.6: + resolution: {integrity: sha512-22wJUOD3zswWFqgwjNHa1965LvqTX87WPu/lreY2KSd7SVcERfuZ4GfUaOnJNnvtoIv2yXT/W00YIGMetXtFXg==} + engines: {node: '>= 0.4'} + dependencies: + array.prototype.map: 1.0.5 + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + get-intrinsic: 1.2.1 + iterate-value: 1.0.2 + dev: true + + /proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: true + + /protocols@2.0.1: + resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + + /proxy-agent@6.3.0: + resolution: {integrity: sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4 + http-proxy-agent: 7.0.0 + https-proxy-agent: 7.0.0 + lru-cache: 7.18.3 + pac-proxy-agent: 7.0.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true + + /punycode@2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + dev: true + + /pupa@3.1.0: + resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} + engines: {node: '>=12.20'} + dependencies: + escape-goat: 4.0.0 + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: true + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /read-package-json-fast@3.0.2: + resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + json-parse-even-better-errors: 3.0.0 + npm-normalize-package-bin: 3.0.0 + dev: true + + /read-pkg-up@10.1.0: + resolution: {integrity: sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==} + engines: {node: '>=16'} + dependencies: + find-up: 6.3.0 + read-pkg: 8.1.0 + type-fest: 4.3.1 + dev: true + + /read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + /read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + /read-pkg@8.1.0: + resolution: {integrity: sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==} + engines: {node: '>=16'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 6.0.0 + parse-json: 7.1.0 + type-fest: 4.3.1 + dev: true + + /readable-stream@3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + dependencies: + resolve: 1.22.2 + dev: true + + /redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + /refa@0.11.0: + resolution: {integrity: sha512-486O8/pQXwj9jV0mVvUnTsxq0uknpBnNJ0eCUhkZqJRQ8KutrT1PhzmumdCeM1hSBF2eMlFPmwECRER4IbKXlQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dependencies: + '@eslint-community/regexpp': 4.6.2 + dev: true + + /regexp-ast-analysis@0.6.0: + resolution: {integrity: sha512-OLxjyjPkVH+rQlBLb1I/P/VTmamSjGkvN5PTV5BXP432k3uVz727J7H29GA5IFiY0m7e1xBN7049Wn59FY3DEQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dependencies: + '@eslint-community/regexpp': 4.6.2 + refa: 0.11.0 + dev: true + + /regexp.prototype.flags@1.5.0: + resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + functions-have-names: 1.2.3 + dev: true + + /registry-auth-token@5.0.1: + resolution: {integrity: sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==} + engines: {node: '>=14'} + dependencies: + '@pnpm/npm-conf': 1.0.5 + dev: true + + /registry-url@6.0.1: + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} + engines: {node: '>=12'} + dependencies: + rc: 1.2.8 + dev: true + + /release-it@16.1.5: + resolution: {integrity: sha512-w/zCljPZBSYcCwR9fjDB1zaYwie1CAQganUrwNqjtXacXhrrsS5E6dDUNLcxm2ypu8GWAgZNMJfuBJqIO2E7fA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@iarna/toml': 2.2.5 + '@octokit/rest': 19.0.13 + async-retry: 1.3.3 + chalk: 5.3.0 + cosmiconfig: 8.2.0 + execa: 7.2.0 + git-url-parse: 13.1.0 + globby: 13.2.2 + got: 13.0.0 + inquirer: 9.2.10 + is-ci: 3.0.1 + issue-parser: 6.0.0 + lodash: 4.17.21 + mime-types: 2.1.35 + new-github-release-url: 2.0.0 + node-fetch: 3.3.2 + open: 9.1.0 + ora: 7.0.1 + os-name: 5.1.0 + promise.allsettled: 1.0.6 + proxy-agent: 6.3.0 + semver: 7.5.4 + shelljs: 0.8.5 + update-notifier: 6.0.2 + url-join: 5.0.0 + wildcard-match: 5.1.2 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: true + + /replace-in-file@7.0.1: + resolution: {integrity: sha512-KbhgPq04eA+TxXuUxpgWIH9k/TjF+28ofon2PXP7vq6izAILhxOtksCVcLuuQLtyjouBaPdlH6RJYYcSPVxCOA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chalk: 4.1.2 + glob: 8.1.0 + yargs: 17.7.2 + dev: false + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + /resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve@1.22.2: + resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + dependencies: + lowercase-keys: 3.0.0 + + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /reverse-arguments@1.0.0: + resolution: {integrity: sha512-/x8uIPdTafBqakK0TmPNJzgkLP+3H+yxpUJhCQHsLBg1rYEVNR2D8BRYNWQhVBjyOd7oo1dZRVzIkwMY2oqfYQ==} + dev: true + + /rfdc@1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup@3.21.1: + resolution: {integrity: sha512-GpUgqWCw56OSiBKf7lcAITstYiBV1/EKaKYPl9r8HgAxc6/qYAVw1PaHWnvHWFziRaf4HsVCDLq/IGtBi1K/Zw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /run-applescript@5.0.0: + resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} + engines: {node: '>=12'} + dependencies: + execa: 5.1.1 + dev: true + + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: true + + /run-con@1.3.2: + resolution: {integrity: sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 4.1.1 + minimist: 1.2.8 + strip-json-comments: 3.1.1 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.5.0 + dev: true + + /safe-array-concat@1.0.0: + resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-regex: 1.1.4 + dev: true + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + + /scslre@0.2.0: + resolution: {integrity: sha512-4hc49fUMmX3jM0XdFUAPBrs1xwEcdHa0KyjEsjFs+Zfc66mpFpq5YmRgDtl+Ffo6AtJIilfei+yKw8fUn3N88w==} + dependencies: + '@eslint-community/regexpp': 4.6.2 + refa: 0.11.0 + regexp-ast-analysis: 0.6.0 + dev: true + + /semver-diff@4.0.0: + resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} + engines: {node: '>=12'} + dependencies: + semver: 7.5.4 + dev: true + + /semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /sentences-per-line@0.2.1: + resolution: {integrity: sha512-6hlyKBwqoaZJ5+RBTKNNem2kBGAboh9e9KfFw5KYKA+64xaTYWbv5C6XnOudx8xk1Sg6f/4yalhJtCZFSLWIsQ==} + dependencies: + markdownlint: 0.11.0 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /shell-quote-word@1.0.1: + resolution: {integrity: sha512-lT297f1WLAdq0A4O+AknIFRP6kkiI3s8C913eJ0XqBxJbZPGWUNkRQk2u8zk4bEAjUJ5i+fSLwB6z1HzeT+DEg==} + dev: true + + /shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + dev: true + + /should-semantic-release@0.1.1: + resolution: {integrity: sha512-gqZVaRzwRenYqfLv5ZLfUHvudjDvpHmdO4yGBark3+OnUA4AGgpwwLD1+aPPH8atXjKPJmplzN+FfKmJi6Ujfw==} + engines: {node: '>=18'} + hasBin: true + dependencies: + '@pkgjs/parseargs': 0.11.0 + conventional-commits-parser: 3.2.4 + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + object-inspect: 1.12.3 + dev: true + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: false + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + + /slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + dev: true + + /smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: true + + /socks-proxy-agent@8.0.1: + resolution: {integrity: sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4 + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + dev: true + + /socks@2.7.1: + resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 2.0.0 + smart-buffer: 4.2.0 + dev: true + + /sort-object-keys@1.1.3: + resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} + dev: true + + /sort-package-json@2.5.1: + resolution: {integrity: sha512-vx/KoZxm8YNMUqdlw7SGTfqR5pqZ/sUfgOuRtDILiOy/3AvzhAibyUe2cY3OpLs3oRSow9up4yLVtQaM24rbDQ==} + hasBin: true + dependencies: + detect-indent: 7.0.1 + detect-newline: 4.0.0 + get-stdin: 9.0.0 + git-hooks-list: 3.0.0 + globby: 13.2.2 + is-plain-obj: 4.1.0 + sort-object-keys: 1.1.3 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: true + + /spdx-correct@3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + + /spdx-license-ids@3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + + /split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + dependencies: + readable-stream: 3.6.0 + + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: true + + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.3.3: + resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} + dev: true + + /stdin-discarder@0.1.0: + resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + bl: 5.1.0 + dev: true + + /string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + + /string-width@6.1.0: + resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} + engines: {node: '>=16'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 10.2.1 + strip-ansi: 7.1.0 + dev: true + + /string.fromcodepoint@0.2.1: + resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} + dev: true + + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string.prototype.trimstart@1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strip-json-comments@5.0.0: + resolution: {integrity: sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==} + engines: {node: '>=14.16'} + dev: true + + /strip-literal@1.0.1: + resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} + dependencies: + acorn: 8.10.0 + dev: true + + /sucrase@3.34.0: + resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==} + engines: {node: '>=8'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.2 + commander: 4.1.1 + glob: 7.1.6 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: true + + /summary@2.1.0: + resolution: {integrity: sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==} + dev: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /synckit@0.8.5: + resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/utils': 2.3.1 + tslib: 2.5.0 + dev: true + + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + + /text-extensions@1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + + /text-extensions@2.4.0: + resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} + engines: {node: '>=8'} + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + + /through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + /tiny-glob@0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + dev: true + + /tinybench@2.5.0: + resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} + dev: true + + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.1.1: + resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==} + engines: {node: '>=14.0.0'} + dev: true + + /title-case@3.0.3: + resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} + dependencies: + tslib: 2.5.0 + dev: false + + /titleize@3.0.0: + resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} + engines: {node: '>=12'} + dev: true + + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-no-case@1.0.2: + resolution: {integrity: sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==} + dev: true + + /to-pascal-case@1.0.0: + resolution: {integrity: sha512-QGMWHqM6xPrcQW57S23c5/3BbYb0Tbe9p+ur98ckRnGDwD4wbbtDiYI38CfmMKNB5Iv0REjs5SNDntTwvDxzZA==} + dependencies: + to-space-case: 1.0.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /to-space-case@1.0.0: + resolution: {integrity: sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==} + dependencies: + to-no-case: 1.0.2 + dev: true + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.1.1 + dev: true + + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + + /trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + + /ts-api-utils@1.0.1(typescript@5.2.2): + resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.2.2 + dev: true + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib@2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + + /tsup@7.2.0(typescript@5.2.2): + resolution: {integrity: sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ==} + engines: {node: '>=16.14'} + hasBin: true + peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.1.0' + peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.0.1(esbuild@0.18.19) + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.3.4 + esbuild: 0.18.19 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.1 + resolve-from: 5.0.0 + rollup: 3.21.1 + source-map: 0.8.0-beta.0 + sucrase: 3.34.0 + tree-kill: 1.2.2 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + + /tsutils@3.21.0(typescript@5.2.2): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.2.2 + dev: true + + /tsx@3.12.7: + resolution: {integrity: sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==} + hasBin: true + dependencies: + '@esbuild-kit/cjs-loader': 2.4.2 + '@esbuild-kit/core-utils': 3.1.0 + '@esbuild-kit/esm-loader': 2.5.5 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + /type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true + + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: true + + /type-fest@3.12.0: + resolution: {integrity: sha512-qj9wWsnFvVEMUDbESiilKeXeHL7FwwiFcogfhfyjmvT968RXSvnl23f1JOClTHYItsi7o501C/7qVllscUP3oA==} + engines: {node: '>=14.16'} + dev: true + + /type-fest@4.3.1: + resolution: {integrity: sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==} + engines: {node: '>=16'} + dev: true + + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.10 + dev: true + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.10 + dev: true + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.10 + dev: true + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.10 + dev: true + + /typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 + dev: true + + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: true + + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + dev: true + + /ufo@1.1.2: + resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} + dev: true + + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /unescape-js@1.1.4: + resolution: {integrity: sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==} + dependencies: + string.fromcodepoint: 0.2.1 + dev: true + + /unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + dependencies: + crypto-random-string: 4.0.0 + dev: true + + /unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + dependencies: + '@types/unist': 2.0.6 + dev: true + + /universal-github-app-jwt@1.1.1: + resolution: {integrity: sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==} + dependencies: + '@types/jsonwebtoken': 9.0.1 + jsonwebtoken: 9.0.0 + dev: false + + /universal-user-agent@6.0.0: + resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} + + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + + /untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + dev: true + + /update-notifier@6.0.2: + resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} + engines: {node: '>=14.16'} + dependencies: + boxen: 7.0.1 + chalk: 5.3.0 + configstore: 6.0.0 + has-yarn: 3.0.0 + import-lazy: 4.0.0 + is-ci: 3.0.1 + is-installed-globally: 0.4.0 + is-npm: 6.0.0 + is-yarn-global: 0.4.1 + latest-version: 7.0.0 + pupa: 3.1.0 + semver: 7.5.4 + semver-diff: 4.0.0 + xdg-basedir: 5.1.0 + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: true + + /url-join@5.0.0: + resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /v8-to-istanbul@9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 + dev: true + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + + /validate-npm-package-name@5.0.0: + resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + builtins: 5.0.1 + dev: true + + /vite-node@0.34.3(@types/node@20.5.6): + resolution: {integrity: sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.4.0 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 4.3.3(@types/node@20.5.6) + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@4.3.3(@types/node@20.5.6): + resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.5.6 + esbuild: 0.17.18 + postcss: 8.4.23 + rollup: 3.21.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitest@0.34.3: + resolution: {integrity: sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.5 + '@types/chai-subset': 1.3.3 + '@types/node': 20.5.6 + '@vitest/expect': 0.34.3 + '@vitest/runner': 0.34.3 + '@vitest/snapshot': 0.34.3 + '@vitest/spy': 0.34.3 + '@vitest/utils': 0.34.3 + acorn: 8.10.0 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.7 + debug: 4.3.4 + local-pkg: 0.4.3 + magic-string: 0.30.1 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.3.3 + strip-literal: 1.0.1 + tinybench: 2.5.0 + tinypool: 0.7.0 + vite: 4.3.3(@types/node@20.5.6) + vite-node: 0.34.3(@types/node@20.5.6) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vlq@0.2.3: + resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} + dev: true + + /vscode-languageserver-textdocument@1.0.8: + resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} + dev: true + + /vscode-uri@3.0.7: + resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} + dev: true + + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true + + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: true + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-typed-array@1.1.11: + resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + dev: true + + /wildcard-match@5.1.2: + resolution: {integrity: sha512-qNXwI591Z88c8bWxp+yjV60Ch4F8Riawe3iGxbzquhy8Xs9m+0+SLFBGb/0yCTIDElawtaImC37fYZ+dr32KqQ==} + dev: true + + /windows-release@5.0.1: + resolution: {integrity: sha512-y1xFdFvdMiDXI3xiOhMbJwt1Y7dUxidha0CWPs1NgjZIjZANTcX7+7bMqNjuezhzb8s5JGEiBAbQjQQYYy7ulw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + execa: 5.1.1 + dev: true + + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + dev: true + + /xdg-basedir@5.1.0: + resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} + engines: {node: '>=12'} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yaml-eslint-parser@1.2.2: + resolution: {integrity: sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==} + engines: {node: ^14.17.0 || >=16.0.0} + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.3.1 + dev: true + + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + dev: true + + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + + /zod-validation-error@1.5.0(zod@3.22.2): + resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} + engines: {node: '>=16.0.0'} + peerDependencies: + zod: ^3.18.0 + dependencies: + zod: 3.22.2 + + /zod@3.22.2: + resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} diff --git a/script/create-test-e2e.js b/script/create-test-e2e.js new file mode 100644 index 00000000..7a6092cd --- /dev/null +++ b/script/create-test-e2e.js @@ -0,0 +1,41 @@ +import { $, execaCommand } from "execa"; +import { strict as assert } from "node:assert"; + +const author = "Test Author"; +const description = "Test description."; +const email = "test@email.com"; +const repository = "create-typescript-app"; +const owner = "TestOwner"; +const title = "Test Title"; + +await $`rm -rf ${repository}`; + +await $({ + stdio: "inherit", +})`c8 -o ./coverage-create -r html -r lcov --src src node ./bin/index.js --base everything --mode create --author ${author} --email ${email} --description ${description} --owner ${owner} --title ${title} --repository ${repository} --skip-all-contributors-api --skip-github-api`; + +process.chdir(repository); + +const failures = []; + +for (const command of [ + `pnpm i`, + `pnpm run build`, + `pnpm run format --list-different`, + `pnpm run lint`, + `pnpm run lint:md`, + `pnpm run lint:package-json`, + `pnpm run lint:packages`, + `pnpm run lint:spelling`, + `pnpm run lint:knip`, + `pnpm run test run`, + `pnpm run tsc`, +]) { + const result = await execaCommand(command, { stdio: "inherit" }); + + if (result.exitCode) { + failures.push({ command, result }); + } +} + +assert.deepEqual(failures, []); diff --git a/script/initialize-test-e2e.js b/script/initialize-test-e2e.js new file mode 100644 index 00000000..3d4350ec --- /dev/null +++ b/script/initialize-test-e2e.js @@ -0,0 +1,52 @@ +import { $ } from "execa"; +import { globby } from "globby"; +import { strict as assert } from "node:assert"; +import fs from "node:fs/promises"; + +const description = "New Description Test"; +const owner = "RNR1"; +const title = "New Title Test"; +const repository = "new-repository-test"; + +// First we run initialize to modifies the local repo, so we can test the changes +await $({ + stdio: "inherit", +})`node ./bin/index.js --description ${description} --base everything --mode initialize --owner ${owner} --title ${title} --repository ${repository} --skip-all-contributors-api --skip-github-api --skip-restore`; + +const newPackageJson = JSON.parse( + (await fs.readFile("./package.json")).toString(), +); +console.log("New package JSON:", newPackageJson); + +assert.equal(newPackageJson.description, description); +assert.equal(newPackageJson.name, repository); + +const files = await globby(["*.*", "**/*.*"], { + gitignore: true, + ignoreFiles: ["script/initialize-test-e2e.js"], +}); + +for (const search of [`/JoshuaKGoldberg/`, "create-typescript-app"]) { + const { stdout } = await $`grep -i ${search} ${files}`; + assert.equal( + stdout, + `README.md:> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).`, + ); +} + +try { + await $`pnpm run lint:knip`; +} catch (error) { + console.error("Error running lint:knip:", error); + process.exitCode = 1; +} + +// Now that initialize has passed normal steps, we reset everything, +// then run again without removing files - so we can capture test coverage +await $`git add -A`; +await $`git reset --hard HEAD`; +await $`pnpm i`; +await $`pnpm run build`; +await $({ + stdio: "inherit", +})`c8 -o ./coverage-initialize -r html -r lcov --src src node ./bin/index.js --base everything --description ${description} --mode initialize --owner ${owner} --title ${title} --repository ${repository} --skip-all-contributors-api --skip-github-api --skip-removal --skip-restore`; diff --git a/script/migrate-test-e2e.js b/script/migrate-test-e2e.js new file mode 100644 index 00000000..ad508234 --- /dev/null +++ b/script/migrate-test-e2e.js @@ -0,0 +1,77 @@ +import chalk from "chalk"; +import { $, execaCommand } from "execa"; + +import packageData from "../package.json" assert { type: "json" }; + +const { description, name: repository } = packageData; +const emailGithub = "github@joshuakgoldberg.com"; +const emailNpm = "npm@joshuakgoldberg.com"; +const owner = "JoshuaKGoldberg"; +const title = "Create TypeScript App"; + +await $({ + stdio: "inherit", +})`c8 -o ./coverage-migrate -r html -r lcov --src src node ./bin/index.js --base everything --mode migrate --description ${description} --email-github ${emailGithub} --email-npm ${emailNpm} --owner ${owner} --title ${title} --repository ${repository} --skip-all-contributors-api --skip-github-api --skip-install`; + +const { stdout: gitStatus } = await $`git status`; +console.log(`Stdout from running \`git status\`:\n${gitStatus}`); + +const indexOfUnstagedFilesMessage = gitStatus.indexOf( + "Changes not staged for commit:", +); +if (indexOfUnstagedFilesMessage === -1) { + throw new Error( + `Looks like migrate didn't cause any file changes? That's ...probably incorrect? 😬`, + ); +} + +const filesExpectedToBeChanged = new Set([ + ".all-contributorsrc", + "bin/index.js", + "README.md", + "knip.jsonc", + "package.json", + "pnpm-lock.yaml", + ".eslintignore", + ".eslintrc.cjs", + ".github/DEVELOPMENT.md", + ".github/workflows/lint.yml", + ".github/workflows/lint-knip.yml", + ".github/workflows/test.yml", + ".gitignore", + ".prettierignore", + "cspell.json", +]); + +const unstagedModifiedFiles = gitStatus + .slice(indexOfUnstagedFilesMessage) + .match(/modified: {3}(\S+)\n/g) + .map((match) => match.split(/\s+/g)[1]) + .filter((filePath) => !filesExpectedToBeChanged.has(filePath)); + +console.log("Unexpected modified files are:", unstagedModifiedFiles); + +if (unstagedModifiedFiles.length) { + const gitDiffCommand = `git diff HEAD -- ${unstagedModifiedFiles.join(" ")}`; + console.log( + `Stdout from running \`${gitDiffCommand}\`:\n${ + (await execaCommand(gitDiffCommand)).stdout + }`, + ); + console.error( + [ + "", + "Oh no! Running the migrate script modified some files:", + ...unstagedModifiedFiles.map((filePath) => ` - ${filePath}`), + "", + "That likely indicates changes made to the repository without", + "corresponding updates to templates in src/.", + "", + "Please search for those file(s)' name(s) under src/migrate for", + "the corresponding template and update those as well.", + ] + .map((line) => chalk.red(line)) + .join("\n"), + ); + process.exitCode = 1; +} diff --git a/src/bin/index.test.ts b/src/bin/index.test.ts new file mode 100644 index 00000000..d76bad6d --- /dev/null +++ b/src/bin/index.test.ts @@ -0,0 +1,190 @@ +import chalk from "chalk"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import z from "zod"; + +import { bin } from "./index.js"; + +const mockCancel = vi.fn(); +const mockOutro = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + get cancel() { + return mockCancel; + }, + intro: vi.fn(), + log: { + info: vi.fn(), + }, + get outro() { + return mockOutro; + }, +})); + +const mockLogLine = vi.fn(); + +vi.mock("../shared/cli/lines.js", () => ({ + get logLine() { + return mockLogLine; + }, +})); + +const mockCreate = vi.fn(); + +vi.mock("../create/index.js", () => ({ + get create() { + return mockCreate; + }, +})); + +const mockInitialize = vi.fn(); + +vi.mock("../initialize/index.js", () => ({ + get initialize() { + return mockInitialize; + }, +})); + +const mockMigrate = vi.fn(); + +vi.mock("../migrate/index.js", () => ({ + get migrate() { + return mockMigrate; + }, +})); + +const mockPromptForMode = vi.fn(); + +vi.mock("./mode.js", () => ({ + get promptForMode() { + return mockPromptForMode; + }, +})); + +describe("bin", () => { + beforeEach(() => { + vi.spyOn(console, "clear").mockImplementation(() => undefined); + }); + + it("returns 1 when promptForMode returns undefined", async () => { + mockPromptForMode.mockResolvedValue(undefined); + + const result = await bin([]); + + expect(mockOutro).toHaveBeenCalledWith( + chalk.red("Operation cancelled. Exiting - maybe another time? 👋"), + ); + expect(result).toBe(1); + }); + + it("returns 1 when promptForMode returns an error", async () => { + const error = new Error("Oh no!"); + mockPromptForMode.mockResolvedValue(error); + + const result = await bin([]); + + expect(mockOutro).toHaveBeenCalledWith(chalk.red(error.message)); + expect(result).toBe(1); + }); + + it("returns the success result of the corresponding runner without cancel logging when promptForMode returns a mode that succeeds", async () => { + const mode = "create"; + const args = ["--owner", "abc123"]; + const code = 0; + + mockPromptForMode.mockResolvedValue(mode); + mockCreate.mockResolvedValue({ code, options: {} }); + + const result = await bin(args); + + expect(mockCreate).toHaveBeenCalledWith(args); + expect(mockCancel).not.toHaveBeenCalled(); + expect(mockInitialize).not.toHaveBeenCalled(); + expect(mockMigrate).not.toHaveBeenCalled(); + expect(result).toEqual(code); + }); + + it("returns the cancel result of the corresponding runner and cancel logs when promptForMode returns a mode that cancels", async () => { + const mode = "create"; + const args = ["--owner", "abc123"]; + const code = 2; + + mockPromptForMode.mockResolvedValue(mode); + mockCreate.mockResolvedValue({ code, options: {} }); + + const result = await bin(args); + + expect(mockCreate).toHaveBeenCalledWith(args); + expect(mockCancel).toHaveBeenCalledWith( + `Operation cancelled. Exiting - maybe another time? 👋`, + ); + expect(result).toEqual(code); + }); + + it("returns the cancel result containing a zod error of the corresponding runner and output plus cancel logs when promptForMode returns a mode that cancels with a string error", async () => { + const mode = "initialize"; + const args = ["--email", "abc123"]; + const code = 2; + const error = "Oh no!"; + + mockPromptForMode.mockResolvedValue(mode); + mockInitialize.mockResolvedValue({ + code: 2, + error, + options: {}, + }); + + const result = await bin(args); + + expect(mockInitialize).toHaveBeenCalledWith(args); + expect(mockLogLine).toHaveBeenCalledWith(chalk.red(error)); + expect(mockCancel).toHaveBeenCalledWith( + `Operation cancelled. Exiting - maybe another time? 👋`, + ); + expect(result).toEqual(code); + }); + + it("returns the cancel result containing a zod error of the corresponding runner and output plus cancel logs when promptForMode returns a mode that cancels with a zod error", async () => { + const mode = "initialize"; + const args = ["--email", "abc123"]; + const code = 2; + + const validationResult = z + .object({ email: z.string().email() }) + .safeParse({ email: "abc123" }); + + mockPromptForMode.mockResolvedValue(mode); + mockInitialize.mockResolvedValue({ + code: 2, + error: (validationResult as z.SafeParseError<{ email: string }>).error, + options: {}, + }); + + const result = await bin(args); + + expect(mockInitialize).toHaveBeenCalledWith(args); + expect(mockLogLine).toHaveBeenCalledWith( + chalk.red('Validation error: Invalid email at "email"'), + ); + expect(mockCancel).toHaveBeenCalledWith( + `Operation cancelled. Exiting - maybe another time? 👋`, + ); + expect(result).toEqual(code); + }); + + it("returns the cancel result of the corresponding runner and cancel logs when promptForMode returns a mode that fails", async () => { + const mode = "create"; + const args = ["--owner", "abc123"]; + const code = 1; + + mockPromptForMode.mockResolvedValue(mode); + mockCreate.mockResolvedValue({ code, options: {} }); + + const result = await bin(args); + + expect(mockCreate).toHaveBeenCalledWith(args); + expect(mockCancel).toHaveBeenCalledWith( + `Operation failed. Exiting - maybe another time? 👋`, + ); + expect(result).toEqual(code); + }); +}); diff --git a/src/bin/index.ts b/src/bin/index.ts new file mode 100644 index 00000000..b8778c31 --- /dev/null +++ b/src/bin/index.ts @@ -0,0 +1,82 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; +import { parseArgs } from "node:util"; +import { fromZodError } from "zod-validation-error"; + +import { createRerunSuggestion } from "../create/createRerunSuggestion.js"; +import { create } from "../create/index.js"; +import { initialize } from "../initialize/index.js"; +import { migrate } from "../migrate/index.js"; +import { logLine } from "../shared/cli/lines.js"; +import { StatusCodes } from "../shared/codes.js"; +import { promptForMode } from "./mode.js"; + +const operationMessage = (verb: string) => + `Operation ${verb}. Exiting - maybe another time? 👋`; + +export async function bin(args: string[]) { + console.clear(); + + prompts.intro( + [ + chalk.greenBright(`Welcome to`), + chalk.bgGreenBright.black(`create-typescript-app`), + chalk.greenBright(`! 🎉`), + ].join(" "), + ); + + logLine(); + logLine( + chalk.yellow( + "⚠️ This template is early stage, opinionated, and not endorsed by the TypeScript team. ⚠️", + ), + ); + logLine( + chalk.yellow( + "⚠️ If any tooling it sets displeases you, you can always remove that portion manually. ⚠️", + ), + ); + + const { values } = parseArgs({ + args, + options: { + mode: { type: "string" }, + }, + strict: false, + }); + + const mode = await promptForMode(values.mode); + if (typeof mode !== "string") { + prompts.outro(chalk.red(mode?.message ?? operationMessage("cancelled"))); + return 1; + } + + const runners = { create, initialize, migrate }; + const { code, error, options } = await runners[mode](args); + + prompts.log.info( + [ + chalk.italic(`Tip: to run again with the same input values, use:`), + chalk.blue(createRerunSuggestion(options)), + ].join(" "), + ); + + if (code) { + logLine(); + + if (error) { + logLine( + chalk.red(typeof error === "string" ? error : fromZodError(error)), + ); + logLine(); + } + + prompts.cancel( + code === StatusCodes.Cancelled + ? operationMessage("cancelled") + : operationMessage("failed"), + ); + } + + return code; +} diff --git a/src/bin/mode.test.ts b/src/bin/mode.test.ts new file mode 100644 index 00000000..bca3bd8c --- /dev/null +++ b/src/bin/mode.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, vi } from "vitest"; + +import { promptForMode } from "./mode.js"; + +const mockSelect = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + isCancel: () => false, + get select() { + return mockSelect; + }, +})); + +describe("promptForMode", () => { + it("returns an error when the input exists and is not a mode", async () => { + const mode = await promptForMode("other"); + + expect(mode).toMatchInlineSnapshot( + "[Error: Unknown --mode: other. Allowed modes are: create, initialize, migrate.]", + ); + }); + + it("returns the input when it is a mode", async () => { + const input = "create"; + + const mode = await promptForMode(input); + + expect(mode).toEqual(input); + }); + + it("returns the selection when input is undefined and the user selects it", async () => { + const mode = "create"; + mockSelect.mockResolvedValue(mode); + + const actual = await promptForMode(undefined); + + expect(actual).toEqual(mode); + }); +}); diff --git a/src/bin/mode.ts b/src/bin/mode.ts new file mode 100644 index 00000000..17c67752 --- /dev/null +++ b/src/bin/mode.ts @@ -0,0 +1,67 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; +import z from "zod"; + +import { StatusCode } from "../shared/codes.js"; +import { filterPromptCancel } from "../shared/prompts.js"; +import { Options } from "../shared/types.js"; + +export interface ModeResult { + code: StatusCode; + error?: string | z.ZodError; + options: Partial; +} + +export type ModeRunner = (args: string[]) => Promise; + +export type Mode = "create" | "initialize" | "migrate"; + +const allowedModes = ["create", "initialize", "migrate"] satisfies Mode[]; + +function isMode(input: boolean | string): input is Mode { + return allowedModes.includes(input as Mode); +} + +export async function promptForMode(input: boolean | string | undefined) { + if (input) { + if (!isMode(input)) { + return new Error( + `Unknown --mode: ${input}. Allowed modes are: ${allowedModes.join( + ", ", + )}.`, + ); + } + + return input; + } + + const label = (base: string, text: string) => `${chalk.bold(base)} ${text}`; + + const selection = filterPromptCancel( + (await prompts.select({ + message: chalk.blue("How would you like to use the template?"), + options: [ + { + label: label("create", "a new repository in a child directory"), + value: "create", + }, + { + label: label( + "initialize", + "a freshly repository in the current directory", + ), + value: "initialize", + }, + { + label: label( + "migrate", + "an existing repository in the current directory", + ), + value: "migrate", + }, + ], + })) as Mode | symbol, + ); + + return selection; +} diff --git a/src/create/createAndEnterRepository.ts b/src/create/createAndEnterRepository.ts new file mode 100644 index 00000000..d36a953a --- /dev/null +++ b/src/create/createAndEnterRepository.ts @@ -0,0 +1,14 @@ +import { $ } from "execa"; +import fs from "node:fs/promises"; + +export async function createAndEnterRepository(repository: string) { + if ((await fs.readdir(".")).includes(repository)) { + return false; + } + + await fs.mkdir(repository); + process.chdir(repository); + await $`git init`; + + return true; +} diff --git a/src/create/createRerunSuggestion.test.ts b/src/create/createRerunSuggestion.test.ts new file mode 100644 index 00000000..43daedc8 --- /dev/null +++ b/src/create/createRerunSuggestion.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from "vitest"; + +import { Options } from "../shared/types.js"; +import { createRerunSuggestion } from "./createRerunSuggestion.js"; + +const options = { + access: "public", + author: "TestAuthor", + base: "everything", + createRepository: true, + description: "Test description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: true, + excludeCompliance: true, + excludeLintJSDoc: true, + excludeLintJson: true, + excludeLintKnip: true, + excludeLintMd: false, + excludeLintPackageJson: true, + excludeLintPackages: false, + excludeLintPerfectionist: true, + excludeLintSpelling: false, + excludeLintYml: false, + excludeReleases: false, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + keywords: ["abc", "def ghi", "jkl mno pqr"], + logo: undefined, + mode: "create", + owner: "TestOwner", + repository: "test-repository", + skipGitHubApi: true, + skipInstall: true, + skipRemoval: true, + skipRestore: undefined, + skipUninstall: undefined, + title: "Test Title", +} satisfies Options; + +describe("createRerunSuggestion", () => { + it("includes key-value pairs with mixed truthy and falsy values", () => { + const actual = createRerunSuggestion(options); + + expect(actual).toMatchInlineSnapshot( + '"npx create-typescript-app --mode create --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-all-contributors true --exclude-compliance true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --keywords \\"abc def ghi jkl mno pqr\\" --mode create --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', + ); + }); + + it("includes stringified logo when it exists", () => { + const actual = createRerunSuggestion({ + ...options, + logo: { + alt: "Test alt.", + src: "test/src.png", + }, + mode: "initialize", + }); + + expect(actual).toMatchInlineSnapshot( + '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-all-contributors true --exclude-compliance true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-package-json true --exclude-lint-perfectionist true --keywords \\"abc def ghi jkl mno pqr\\" --logo test/src.png --logo-alt \\"Test alt.\\" --mode initialize --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', + ); + }); + + it("includes exclusions when they exist", () => { + const actual = createRerunSuggestion({ + ...options, + excludeCompliance: true, + excludeLintMd: true, + excludeLintSpelling: true, + mode: "initialize", + }); + + expect(actual).toMatchInlineSnapshot( + '"npx create-typescript-app --mode initialize --base everything --access public --author TestAuthor --create-repository true --description \\"Test description.\\" --email-github github@email.com --email-npm npm@email.com --exclude-all-contributors true --exclude-compliance true --exclude-lint-jsdoc true --exclude-lint-json true --exclude-lint-knip true --exclude-lint-md true --exclude-lint-package-json true --exclude-lint-perfectionist true --exclude-lint-spelling true --keywords \\"abc def ghi jkl mno pqr\\" --mode initialize --owner TestOwner --repository test-repository --skip-github-api true --skip-install true --skip-removal true --title \\"Test Title\\""', + ); + }); +}); diff --git a/src/create/createRerunSuggestion.ts b/src/create/createRerunSuggestion.ts new file mode 100644 index 00000000..7179e641 --- /dev/null +++ b/src/create/createRerunSuggestion.ts @@ -0,0 +1,53 @@ +import { allArgOptions } from "../shared/options/args.js"; +import { Options } from "../shared/types.js"; + +function getFirstMatchingArg(key: string) { + return Object.keys(allArgOptions).find( + (arg) => arg.replaceAll("-", "") === key.toLowerCase(), + ); +} + +export function createRerunSuggestion(options: Partial): string { + const optionsNormalized = { + ...options, + ...(options.email + ? { + email: undefined, + emailGitHub: options.email.github, + emailNpm: options.email.npm, + } + : { email: undefined }), + ...(options.logo + ? { + logo: options.logo.src, + logoAlt: options.logo.alt, + } + : { logo: undefined }), + }; + + const args = Object.entries(optionsNormalized) + .sort(([a], [b]) => + a === "base" ? -1 : b === "base" ? 1 : a.localeCompare(b), + ) + .filter(([, value]) => !!value) + .map(([key, value]) => { + return `--${getFirstMatchingArg(key)} ${stringifyValue(value)}`; + }) + .join(" "); + + return `npx create-typescript-app --mode ${options.mode} ${args}`; +} + +function stringifyValue( + value: boolean | string | string[] | undefined, +): string { + if (Array.isArray(value)) { + return stringifyValue(value.join(" ")); + } + + const valueStringified = `${value}`; + + return valueStringified.includes(" ") + ? `"${valueStringified}"` + : valueStringified; +} diff --git a/src/create/createWithOptions.ts b/src/create/createWithOptions.ts new file mode 100644 index 00000000..8a001d0c --- /dev/null +++ b/src/create/createWithOptions.ts @@ -0,0 +1,68 @@ +import * as prompts from "@clack/prompts"; +import { $ } from "execa"; + +import { withSpinner, withSpinners } from "../shared/cli/spinners.js"; +import { doesRepositoryExist } from "../shared/doesRepositoryExist.js"; +import { GitHubAndOptions } from "../shared/options/readOptions.js"; +import { addToolAllContributors } from "../steps/addToolAllContributors.js"; +import { finalizeDependencies } from "../steps/finalizeDependencies.js"; +import { initializeGitHubRepository } from "../steps/initializeGitHubRepository/index.js"; +import { runCommands } from "../steps/runCommands.js"; +import { writeReadme } from "../steps/writeReadme/index.js"; +import { writeStructure } from "../steps/writing/writeStructure.js"; + +export async function createWithOptions({ github, options }: GitHubAndOptions) { + await withSpinners("Creating repository structure", [ + [ + "Writing structure", + async () => { + await writeStructure(options); + }, + ], + [ + "Writing README.md", + async () => { + await writeReadme(options); + }, + ], + ]); + + if (!options.excludeAllContributors && !options.skipAllContributorsApi) { + await withSpinner("Adding contributors to table", async () => { + await addToolAllContributors(options); + }); + } + + if (!options.skipInstall) { + await withSpinner("Installing packages", async () => + finalizeDependencies(options), + ); + } + + await runCommands("Cleaning up files", [ + "pnpm dedupe", + "pnpm lint --fix", + "pnpm format --write", + ]); + + const sendToGitHub = + github && + (await doesRepositoryExist(github.octokit, options)) && + (options.createRepository ?? + (await prompts.confirm({ + message: + "Would you like to push the template's tooling up to the repository on GitHub?", + })) === true); + + if (sendToGitHub) { + await withSpinner("Initializing GitHub repository", async () => { + await $`git remote add origin https://github.com/${options.owner}/${options.repository}`; + await $`git add -A`; + await $`git commit --message ${"feat: initialized repo ✨"}`; + await $`git push -u origin main --force`; + await initializeGitHubRepository(github.octokit, options); + }); + } + + return { sentToGitHub: sendToGitHub }; +} diff --git a/src/create/index.test.ts b/src/create/index.test.ts new file mode 100644 index 00000000..5030651a --- /dev/null +++ b/src/create/index.test.ts @@ -0,0 +1,71 @@ +import chalk from "chalk"; +import { describe, expect, it, vi } from "vitest"; + +import { StatusCodes } from "../shared/codes.js"; +import { create } from "./index.js"; + +const mockOutro = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + get outro() { + return mockOutro; + }, + spinner: vi.fn(), +})); + +const mockReadOptions = vi.fn(); + +vi.mock("../shared/options/readOptions.js", () => ({ + get readOptions() { + return mockReadOptions; + }, +})); + +const mockCreateAndEnterRepository = vi.fn(); + +vi.mock("./createAndEnterRepository.js", () => ({ + get createAndEnterRepository() { + return mockCreateAndEnterRepository; + }, +})); + +const optionsBase = { + repository: "TestRepository", +}; + +describe("create", () => { + it("returns a cancellation code when readOptions cancels", async () => { + mockReadOptions.mockResolvedValue({ + cancelled: true, + options: optionsBase, + }); + + const result = await create([]); + + expect(result).toEqual({ + code: StatusCodes.Cancelled, + options: optionsBase, + }); + }); + + it("returns a failure code when createAndEnterRepository returns false", async () => { + mockReadOptions.mockResolvedValue({ + cancelled: false, + options: optionsBase, + }); + + mockCreateAndEnterRepository.mockResolvedValue(false); + + const result = await create([]); + + expect(result).toEqual({ + code: StatusCodes.Failure, + options: optionsBase, + }); + expect(mockOutro).toHaveBeenCalledWith( + chalk.red( + "The TestRepository directory already exists. Please remove the directory or try a different name.", + ), + ); + }); +}); diff --git a/src/create/index.ts b/src/create/index.ts new file mode 100644 index 00000000..fddea49a --- /dev/null +++ b/src/create/index.ts @@ -0,0 +1,69 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; + +import { ModeResult } from "../bin/mode.js"; +import { outro } from "../shared/cli/outro.js"; +import { StatusCodes } from "../shared/codes.js"; +import { generateNextSteps } from "../shared/generateNextSteps.js"; +import { readOptions } from "../shared/options/readOptions.js"; +import { runOrRestore } from "../shared/runOrRestore.js"; +import { createAndEnterRepository } from "./createAndEnterRepository.js"; +import { createRerunSuggestion } from "./createRerunSuggestion.js"; +import { createWithOptions } from "./createWithOptions.js"; + +export async function create(args: string[]): Promise { + const inputs = await readOptions(args, "create"); + if (inputs.cancelled) { + return { + code: StatusCodes.Cancelled, + error: inputs.error, + options: inputs.options, + }; + } + + const wat = await createAndEnterRepository(inputs.options.repository); + if (!wat) { + prompts.outro( + chalk.red( + `The ${inputs.options.repository} directory already exists. Please remove the directory or try a different name.`, + ), + ); + return { code: StatusCodes.Failure, options: inputs.options }; + } + + return { + code: await runOrRestore({ + run: async () => { + const { sentToGitHub } = await createWithOptions(inputs); + const nextSteps = generateNextSteps(inputs.options); + + outro( + sentToGitHub + ? nextSteps + : [ + { + label: + "Consider creating a GitHub repository from the new directory:", + lines: [ + `cd ${inputs.options.repository}`, + createRerunSuggestion({ + ...inputs.options, + mode: "initialize", + skipGitHubApi: false, + skipInstall: false, + }), + `git add -A`, + `git commit -m "feat: initial commit ✨"`, + `git push -u origin main`, + ], + variant: "code", + }, + ...nextSteps, + ], + ); + }, + skipRestore: inputs.options.skipRestore, + }), + options: inputs.options, + }; +} diff --git a/src/greet.test.ts b/src/greet.test.ts new file mode 100644 index 00000000..f729115f --- /dev/null +++ b/src/greet.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it, vi } from "vitest"; + +import { greet } from "./greet.js"; + +const message = "Yay, testing!"; + +describe("greet", () => { + it("logs to the console once when message is provided as a string", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet(message); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs to the console once when message is provided as an object", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet({ message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs once when times is not provided in an object", () => { + const logger = vi.fn(); + + greet({ logger, message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs a specified number of times when times is provided", () => { + const logger = vi.fn(); + const times = 7; + + greet({ logger, message, times }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(7); + }); +}); diff --git a/src/greet.ts b/src/greet.ts new file mode 100644 index 00000000..a0d3b4c6 --- /dev/null +++ b/src/greet.ts @@ -0,0 +1,13 @@ +import { GreetOptions } from "./types.js"; + +export function greet(options: GreetOptions | string) { + const { + logger = console.log.bind(console), + message, + times = 1, + } = typeof options === "string" ? { message: options } : options; + + for (let i = 0; i < times; i += 1) { + logger(message); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..a39b40fa --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from "./greet.js"; +export * from "./types.js"; diff --git a/src/initialize/index.test.ts b/src/initialize/index.test.ts new file mode 100644 index 00000000..675aaa6e --- /dev/null +++ b/src/initialize/index.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it, vi } from "vitest"; + +import { StatusCodes } from "../shared/codes.js"; +import { initialize } from "./index.js"; + +const mockOutro = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + get outro() { + return mockOutro; + }, + spinner: vi.fn(), +})); + +const mockReadOptions = vi.fn(); + +vi.mock("../shared/options/readOptions.js", () => ({ + get readOptions() { + return mockReadOptions; + }, +})); + +const mockInitializeAndEnterRepository = vi.fn(); + +vi.mock("./initializeAndEnterRepository.js", () => ({ + get initializeAndEnterRepository() { + return mockInitializeAndEnterRepository; + }, +})); + +const optionsBase = { + repository: "TestRepository", +}; + +describe("initialize", () => { + it("returns a cancellation code when readOptions cancels", async () => { + mockReadOptions.mockResolvedValue({ + cancelled: true, + options: optionsBase, + }); + + const result = await initialize([]); + + expect(result).toEqual({ + code: StatusCodes.Cancelled, + options: optionsBase, + }); + }); +}); diff --git a/src/initialize/index.ts b/src/initialize/index.ts new file mode 100644 index 00000000..69c95469 --- /dev/null +++ b/src/initialize/index.ts @@ -0,0 +1,44 @@ +import { ModeRunner } from "../bin/mode.js"; +import { outro } from "../shared/cli/outro.js"; +import { StatusCodes } from "../shared/codes.js"; +import { ensureGitRepository } from "../shared/ensureGitRepository.js"; +import { generateNextSteps } from "../shared/generateNextSteps.js"; +import { readOptions } from "../shared/options/readOptions.js"; +import { runOrRestore } from "../shared/runOrRestore.js"; +import { initializeWithOptions } from "./initializeWithOptions.js"; + +export const initialize: ModeRunner = async (args) => { + const inputs = await readOptions(args, "initialize"); + if (inputs.cancelled) { + return { + code: StatusCodes.Cancelled, + error: inputs.error, + options: inputs.options, + }; + } + + await ensureGitRepository(); + + return { + code: await runOrRestore({ + run: async () => { + await initializeWithOptions(inputs); + + outro([ + { + label: "You may consider committing these changes:", + lines: [ + `git add -A`, + `git commit -m "feat: initialized repo ✨`, + `git push`, + ], + variant: "code", + }, + ...generateNextSteps(inputs.options), + ]); + }, + skipRestore: inputs.options.skipRestore, + }), + options: inputs.options, + }; +}; diff --git a/src/initialize/initializeWithOptions.ts b/src/initialize/initializeWithOptions.ts new file mode 100644 index 00000000..83ff2b01 --- /dev/null +++ b/src/initialize/initializeWithOptions.ts @@ -0,0 +1,62 @@ +import { withSpinner, withSpinners } from "../shared/cli/spinners.js"; +import { GitHubAndOptions } from "../shared/options/readOptions.js"; +import { addOwnerAsAllContributor } from "../steps/addOwnerAsAllContributor.js"; +import { clearChangelog } from "../steps/clearChangelog.js"; +import { initializeGitHubRepository } from "../steps/initializeGitHubRepository/index.js"; +import { removeSetupScripts } from "../steps/removeSetupScripts.js"; +import { resetGitTags } from "../steps/resetGitTags.js"; +import { runCommands } from "../steps/runCommands.js"; +import { uninstallPackages } from "../steps/uninstallPackages.js"; +import { updateAllContributorsTable } from "../steps/updateAllContributorsTable.js"; +import { updateLocalFiles } from "../steps/updateLocalFiles.js"; +import { updateReadme } from "../steps/updateReadme.js"; + +export async function initializeWithOptions({ + github, + options, +}: GitHubAndOptions) { + await withSpinners("Initializing local files", [ + [ + "Updating local files", + async () => { + await updateLocalFiles(options); + }, + ], + ["Updating README.md", updateReadme], + ["Clearing changelog", clearChangelog], + [ + "Updating all-contributors table", + async () => { + await updateAllContributorsTable(options); + }, + ], + ["Resetting Git tags", resetGitTags], + ]); + + if (!options.excludeAllContributors) { + await withSpinner("Updating existing contributor details", async () => { + await addOwnerAsAllContributor(options); + }); + } + + if (github) { + await withSpinner("Initializing GitHub repository", async () => { + await initializeGitHubRepository(github.octokit, options); + }); + } + + if (!options.skipRemoval) { + await withSpinner("Removing setup scripts", removeSetupScripts); + } + + if (!options.skipUninstall) { + await withSpinner("Uninstalling initialization-only packages", async () => + uninstallPackages(options.offline), + ); + } + + await runCommands("Cleaning up files", [ + "pnpm lint --fix", + "pnpm format --write", + ]); +} diff --git a/src/migrate/index.test.ts b/src/migrate/index.test.ts new file mode 100644 index 00000000..87948095 --- /dev/null +++ b/src/migrate/index.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it, vi } from "vitest"; + +import { StatusCodes } from "../shared/codes.js"; +import { migrate } from "./index.js"; + +const mockOutro = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + get outro() { + return mockOutro; + }, + spinner: vi.fn(), +})); + +const mockReadOptions = vi.fn(); + +vi.mock("../shared/options/readOptions.js", () => ({ + get readOptions() { + return mockReadOptions; + }, +})); + +const mockMigrateAndEnterRepository = vi.fn(); + +vi.mock("./migrateAndEnterRepository.js", () => ({ + get migrateAndEnterRepository() { + return mockMigrateAndEnterRepository; + }, +})); + +const optionsBase = { + repository: "TestRepository", +}; + +describe("migrate", () => { + it("returns a cancellation code when readOptions cancels", async () => { + mockReadOptions.mockResolvedValue({ + cancelled: true, + options: optionsBase, + }); + + const result = await migrate([]); + + expect(result).toEqual({ + code: StatusCodes.Cancelled, + options: optionsBase, + }); + }); +}); diff --git a/src/migrate/index.ts b/src/migrate/index.ts new file mode 100644 index 00000000..ecb21936 --- /dev/null +++ b/src/migrate/index.ts @@ -0,0 +1,44 @@ +import { ModeRunner } from "../bin/mode.js"; +import { outro } from "../shared/cli/outro.js"; +import { StatusCodes } from "../shared/codes.js"; +import { ensureGitRepository } from "../shared/ensureGitRepository.js"; +import { generateNextSteps } from "../shared/generateNextSteps.js"; +import { readOptions } from "../shared/options/readOptions.js"; +import { runOrRestore } from "../shared/runOrRestore.js"; +import { migrateWithOptions } from "./migrateWithOptions.js"; + +export const migrate: ModeRunner = async (args) => { + const inputs = await readOptions(args, "migrate"); + if (inputs.cancelled) { + return { + code: StatusCodes.Cancelled, + error: inputs.error, + options: inputs.options, + }; + } + + await ensureGitRepository(); + + return { + code: await runOrRestore({ + run: async () => { + await migrateWithOptions(inputs); + + outro([ + { + label: "You may consider committing these changes:", + lines: [ + `git add -A`, + `git commit -m "migrated repo to create-typescript-app ✨`, + `git push`, + ], + variant: "code", + }, + ...generateNextSteps(inputs.options), + ]); + }, + skipRestore: inputs.options.skipRestore, + }), + options: inputs.options, + }; +}; diff --git a/src/migrate/migrateWithOptions.ts b/src/migrate/migrateWithOptions.ts new file mode 100644 index 00000000..01e4a329 --- /dev/null +++ b/src/migrate/migrateWithOptions.ts @@ -0,0 +1,67 @@ +import { withSpinner, withSpinners } from "../shared/cli/spinners.js"; +import { GitHubAndOptions } from "../shared/options/readOptions.js"; +import { clearUnnecessaryFiles } from "../steps/clearUnnecessaryFiles.js"; +import { detectExistingContributors } from "../steps/detectExistingContributors.js"; +import { finalizeDependencies } from "../steps/finalizeDependencies.js"; +import { initializeGitHubRepository } from "../steps/initializeGitHubRepository/index.js"; +import { runCommands } from "../steps/runCommands.js"; +import { updateAllContributorsTable } from "../steps/updateAllContributorsTable.js"; +import { updateLocalFiles } from "../steps/updateLocalFiles.js"; +import { writeReadme } from "../steps/writeReadme/index.js"; +import { writeStructure } from "../steps/writing/writeStructure.js"; + +export async function migrateWithOptions({ + github, + options, +}: GitHubAndOptions) { + await withSpinners("Migrating repository structure", [ + ["Clearing unnecessary files", clearUnnecessaryFiles], + [ + "Writing structure", + async () => { + await writeStructure(options); + }, + ], + [ + "Writing README.md", + async () => { + await writeReadme(options); + }, + ], + [ + "Updating local files", + async () => { + await updateLocalFiles(options); + }, + ], + [ + "Updating all-contributors table", + async () => { + await updateAllContributorsTable(options); + }, + ], + ]); + + if (github) { + await withSpinner("Initializing GitHub repository", async () => { + await initializeGitHubRepository(github.octokit, options); + }); + } + + if (!options.excludeAllContributors && !options.skipAllContributorsApi) { + await withSpinner("Detecting existing contributors", async () => + detectExistingContributors(github?.auth, options), + ); + } + + if (!options.skipInstall) { + await withSpinner("Installing packages", async () => + finalizeDependencies(options), + ); + } + + await runCommands("Cleaning up files", [ + "pnpm lint --fix", + "pnpm format --write", + ]); +} diff --git a/src/shared/__snapshots__/generateNextSteps.test.ts.snap b/src/shared/__snapshots__/generateNextSteps.test.ts.snap new file mode 100644 index 00000000..df9a0ec6 --- /dev/null +++ b/src/shared/__snapshots__/generateNextSteps.test.ts.snap @@ -0,0 +1,183 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":false,"excludeRenovate":false,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the GitHub apps: + - Codecov (https://github.com/apps/codecov) + - Renovate (https://github.com/apps/renovate)", + "- populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":false,"excludeRenovate":false,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the Renovate GitHub app (https://github.com/apps/renovate).", + "- populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":false,"excludeRenovate":true,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the Codecov GitHub app (https://github.com/apps/codecov).", + "- populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":false,"excludeRenovate":true,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":true,"excludeRenovate":false,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the GitHub apps: + - Codecov (https://github.com/apps/codecov) + - Renovate (https://github.com/apps/renovate)", + "- populate the ACCESS_TOKEN secret (a GitHub PAT with repo and workflow permissions).", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":true,"excludeRenovate":false,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the Renovate GitHub app (https://github.com/apps/renovate).", + "- populate the ACCESS_TOKEN secret (a GitHub PAT with repo and workflow permissions).", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":true,"excludeRenovate":true,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the Codecov GitHub app (https://github.com/apps/codecov).", + "- populate the ACCESS_TOKEN secret (a GitHub PAT with repo and workflow permissions).", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":false,"excludeReleases":true,"excludeRenovate":true,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to populate the ACCESS_TOKEN secret (a GitHub PAT with repo and workflow permissions).", + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":false,"excludeRenovate":false,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the GitHub apps: + - Codecov (https://github.com/apps/codecov) + - Renovate (https://github.com/apps/renovate)", + "- populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":false,"excludeRenovate":false,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the Renovate GitHub app (https://github.com/apps/renovate).", + "- populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":false,"excludeRenovate":true,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to:", + "lines": [ + "- enable the Codecov GitHub app (https://github.com/apps/codecov).", + "- populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + ], + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":false,"excludeRenovate":true,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to populate the secrets: + - ACCESS_TOKEN (a GitHub PAT with repo and workflow permissions) + - NPM_TOKEN (an npm access token with automation permissions)", + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":true,"excludeRenovate":false,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to enable the GitHub apps: + - Codecov (https://github.com/apps/codecov) + - Renovate (https://github.com/apps/renovate)", + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":true,"excludeRenovate":false,"excludeTests":true} 1`] = ` +[ + { + "label": "Be sure to enable the Renovate GitHub app (https://github.com/apps/renovate).", + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":true,"excludeRenovate":true,"excludeTests":false} 1`] = ` +[ + { + "label": "Be sure to enable the Codecov GitHub app (https://github.com/apps/codecov).", + }, +] +`; + +exports[`generateNextSteps > {"excludeAllContributors":true,"excludeReleases":true,"excludeRenovate":true,"excludeTests":true} 1`] = `[]`; diff --git a/src/shared/cli/lines.ts b/src/shared/cli/lines.ts new file mode 100644 index 00000000..d1075ad4 --- /dev/null +++ b/src/shared/cli/lines.ts @@ -0,0 +1,14 @@ +import chalk from "chalk"; + +export function logLine(line?: string) { + console.log(makeLine(line)); +} + +export function logNewSection(line: string) { + logLine(); + console.log(`◇ ${line}`); +} + +export function makeLine(line: string | undefined) { + return [chalk.gray("│"), line].filter(Boolean).join(" "); +} diff --git a/src/shared/cli/lowerFirst.ts b/src/shared/cli/lowerFirst.ts new file mode 100644 index 00000000..3b96e98f --- /dev/null +++ b/src/shared/cli/lowerFirst.ts @@ -0,0 +1,3 @@ +export function lowerFirst(text: string) { + return text[0].toLowerCase() + text.slice(1); +} diff --git a/src/shared/cli/outro.test.ts b/src/shared/cli/outro.test.ts new file mode 100644 index 00000000..cf27b4a4 --- /dev/null +++ b/src/shared/cli/outro.test.ts @@ -0,0 +1,61 @@ +import chalk from "chalk"; +import { SpyInstance, beforeEach, describe, expect, it, vi } from "vitest"; + +import { outro } from "./outro.js"; + +const mockOutro = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + get outro() { + return mockOutro; + }, +})); + +let mockConsoleLog: SpyInstance; + +describe("outro", () => { + beforeEach(() => { + mockConsoleLog = vi + .spyOn(console, "log") + .mockImplementation(() => undefined); + }); + + it("logs only basic statements when no lines are provided", () => { + outro([{ label: "Abc 123" }]); + + expect(mockConsoleLog.mock.calls).toEqual([ + [chalk.blue("Abc 123")], + [], + [chalk.greenBright(`See ya! 👋`)], + [], + ]); + }); + + it("also logs lines when provided", () => { + outro([{ label: "Abc 123", lines: ["one", "two"] }]); + + expect(mockConsoleLog.mock.calls).toEqual([ + [chalk.blue("Abc 123")], + [], + ["one"], + ["two"], + [], + [chalk.greenBright(`See ya! 👋`)], + [], + ]); + }); + + it("logs lines as code when variant is specified", () => { + outro([{ label: "Abc 123", lines: ["one", "two"], variant: "code" }]); + + expect(mockConsoleLog.mock.calls).toEqual([ + [chalk.blue("Abc 123")], + [], + [chalk.gray("one")], + [chalk.gray("two")], + [], + [chalk.greenBright(`See ya! 👋`)], + [], + ]); + }); +}); diff --git a/src/shared/cli/outro.ts b/src/shared/cli/outro.ts new file mode 100644 index 00000000..444891de --- /dev/null +++ b/src/shared/cli/outro.ts @@ -0,0 +1,28 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; + +export interface OutroGroup { + label: string; + lines?: string[]; + variant?: "code"; +} + +export function outro(groups: OutroGroup[]) { + prompts.outro(chalk.blue(`Great, looks like the script finished! 🎉`)); + + for (const { label, lines, variant } of groups) { + console.log(chalk.blue(label)); + console.log(); + + if (lines) { + for (const line of lines) { + console.log(variant === "code" ? chalk.gray(line) : line); + } + + console.log(); + } + } + + console.log(chalk.greenBright(`See ya! 👋`)); + console.log(); +} diff --git a/src/shared/cli/spinners.ts b/src/shared/cli/spinners.ts new file mode 100644 index 00000000..adb8fa92 --- /dev/null +++ b/src/shared/cli/spinners.ts @@ -0,0 +1,71 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; +import readline from "readline"; + +import { logLine, logNewSection, makeLine } from "./lines.js"; +import { lowerFirst } from "./lowerFirst.js"; +import { startLineWithDots } from "./startLineWithDots.js"; + +const s = prompts.spinner(); + +export type SpinnerTask = () => Promise; + +export type LabeledSpinnerTask = [string, SpinnerTask]; + +export async function withSpinner( + label: string, + task: SpinnerTask, +) { + s.start(`${label}...`); + + try { + const result = await task(); + + s.stop(chalk.green(`✅ Passed ${lowerFirst(label)}.`)); + + return result; + } catch (error) { + s.stop(chalk.red(`❌ Error ${lowerFirst(label)}.`)); + + throw new Error(`Failed ${lowerFirst(label)}`, { cause: error }); + } +} + +export async function withSpinners( + label: string, + tasks: LabeledSpinnerTask[], +) { + logNewSection(`${label}...`); + + let currentLabel!: string; + let lastLogged!: string; + + try { + for (const [label, run] of tasks) { + currentLabel = label; + + const line = makeLine(chalk.gray(` - ${label}`)); + const stopWriting = startLineWithDots(line); + + await run(); + + const lineLength = stopWriting(); + lastLogged = chalk.gray(`${line} ✔️\n`); + + readline.clearLine(process.stdout, -1); + readline.moveCursor(process.stdout, -lineLength, 0); + process.stdout.write(lastLogged); + } + + readline.moveCursor(process.stdout, -lastLogged.length, -tasks.length - 2); + readline.clearScreenDown(process.stdout); + + logNewSection(chalk.green(`✅ Passed ${lowerFirst(label)}.`)); + } catch (error) { + const descriptor = `${lowerFirst(label)} > ${lowerFirst(currentLabel)}`; + + logLine(chalk.red(`❌ Error ${descriptor}.`)); + + throw new Error(`Failed ${descriptor}`, { cause: error }); + } +} diff --git a/src/shared/cli/startLineWithDots.ts b/src/shared/cli/startLineWithDots.ts new file mode 100644 index 00000000..e8037816 --- /dev/null +++ b/src/shared/cli/startLineWithDots.ts @@ -0,0 +1,38 @@ +import readline from "readline"; + +export function startLineWithDots(line: string) { + const timer = [setTimeout(tick, 500)]; + let dots = 0; + let lastLogged!: string; + + function clearLine() { + readline.clearLine(process.stdout, -1); + readline.moveCursor(process.stdout, -lastLogged.length, 0); + } + + function writeLine() { + dots = (dots + 1) % 4; + + const toLog = `${line}${".".repeat(dots)}`; + + process.stdout.write(toLog); + + lastLogged = toLog; + + return toLog; + } + + function tick() { + clearLine(); + writeLine(); + timer[0] = setTimeout(tick, 500); + } + + writeLine(); + + return () => { + clearLine(); + clearInterval(timer[0]); + return lastLogged.length; + }; +} diff --git a/src/shared/codes.ts b/src/shared/codes.ts new file mode 100644 index 00000000..89b39571 --- /dev/null +++ b/src/shared/codes.ts @@ -0,0 +1,7 @@ +export const StatusCodes = { + Cancelled: 2, + Failure: 1, + Success: 0, +} as const; + +export type StatusCode = (typeof StatusCodes)[keyof typeof StatusCodes]; diff --git a/src/shared/doesRepositoryExist.test.ts b/src/shared/doesRepositoryExist.test.ts new file mode 100644 index 00000000..d481e6ec --- /dev/null +++ b/src/shared/doesRepositoryExist.test.ts @@ -0,0 +1,47 @@ +import { Octokit } from "octokit"; +import { SpyInstance, describe, expect, it, vi } from "vitest"; + +import { doesRepositoryExist } from "./doesRepositoryExist.js"; + +const createMockOctokit = ( + repos: Partial>, +) => + ({ + rest: { + repos, + }, + }) as unknown as Octokit; + +const owner = "StubOwner"; +const repository = "stub-repository"; + +describe("doesRepositoryExist", () => { + it("returns true when the octokit GET resolves", async () => { + const octokit = createMockOctokit({ get: vi.fn().mockResolvedValue({}) }); + + const actual = await doesRepositoryExist(octokit, { owner, repository }); + + expect(actual).toBe(true); + }); + + it("returns false when the octokit GET rejects with a 404", async () => { + const octokit = createMockOctokit({ + get: vi.fn().mockRejectedValue({ status: 404 }), + }); + + const actual = await doesRepositoryExist(octokit, { owner, repository }); + + expect(actual).toBe(false); + }); + + it("throws the error when awaiting the octokit GET throws a non-404 error", async () => { + const error = new Error("Oh no!"); + const octokit = createMockOctokit({ + get: vi.fn().mockRejectedValue(error), + }); + + await expect( + async () => await doesRepositoryExist(octokit, { owner, repository }), + ).rejects.toEqual(error); + }); +}); diff --git a/src/shared/doesRepositoryExist.ts b/src/shared/doesRepositoryExist.ts new file mode 100644 index 00000000..8868a2f9 --- /dev/null +++ b/src/shared/doesRepositoryExist.ts @@ -0,0 +1,27 @@ +import { Octokit, RequestError } from "octokit"; + +export interface DoesRepositoryExistOptions { + owner: string; + repository: string; +} + +export async function doesRepositoryExist( + octokit: Octokit, + options: DoesRepositoryExistOptions, +) { + // Because the Octokit SDK throws on 404s (😡), + // we try/catch to check whether the repo exists. + try { + await octokit.rest.repos.get({ + owner: options.owner, + repo: options.repository, + }); + return true; + } catch (error) { + if ((error as RequestError).status !== 404) { + throw error; + } + + return false; + } +} diff --git a/src/shared/ensureGitRepository.test.ts b/src/shared/ensureGitRepository.test.ts new file mode 100644 index 00000000..a9b4b7ec --- /dev/null +++ b/src/shared/ensureGitRepository.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it, vi } from "vitest"; + +import { ensureGitRepository } from "./ensureGitRepository.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +describe("ensureGitRepository", () => { + it("does not run git init when git status succeeds", async () => { + mock$.mockResolvedValue(0); + + await ensureGitRepository(); + + expect(mock$).toHaveBeenCalledTimes(1); + }); + + it("runs git init when git status fails", async () => { + mock$.mockRejectedValueOnce(1); + + await ensureGitRepository(); + + expect(mock$).toHaveBeenCalledWith(["git init"]); + }); +}); diff --git a/src/shared/ensureGitRepository.ts b/src/shared/ensureGitRepository.ts new file mode 100644 index 00000000..97ad7074 --- /dev/null +++ b/src/shared/ensureGitRepository.ts @@ -0,0 +1,9 @@ +import { $ } from "execa"; + +export async function ensureGitRepository() { + try { + await $`git status`; + } catch { + await $`git init`; + } +} diff --git a/src/shared/generateNextSteps.test.ts b/src/shared/generateNextSteps.test.ts new file mode 100644 index 00000000..3a000e09 --- /dev/null +++ b/src/shared/generateNextSteps.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "vitest"; + +import { generateNextSteps } from "./generateNextSteps.js"; +import { Options } from "./types.js"; + +const options = { + access: "public", + base: "everything", + description: "Test description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + logo: undefined, + mode: "create", + owner: "TestOwner", + repository: "test-repository", + title: "Test Title", +} satisfies Options; + +describe("generateNextSteps", () => { + for (const excludeAllContributors of [false, true]) { + for (const excludeReleases of [false, true]) { + for (const excludeRenovate of [false, true]) { + for (const excludeTests of [false, true]) { + test( + // eslint-disable-next-line vitest/valid-title + JSON.stringify({ + excludeAllContributors, + excludeReleases, + excludeRenovate, + excludeTests, + }), + () => { + expect( + generateNextSteps({ + ...options, + excludeAllContributors, + excludeReleases, + excludeRenovate, + excludeTests, + }), + ).toMatchSnapshot(); + }, + ); + } + } + } + } +}); diff --git a/src/shared/generateNextSteps.ts b/src/shared/generateNextSteps.ts new file mode 100644 index 00000000..c8eb8b99 --- /dev/null +++ b/src/shared/generateNextSteps.ts @@ -0,0 +1,69 @@ +import { OutroGroup } from "./cli/outro.js"; +import { Options } from "./types.js"; + +export function generateNextSteps(options: Options): OutroGroup[] { + const lines = [ + joinedMessage( + "enable the", + [ + options.excludeTests || ["Codecov", "https://github.com/apps/codecov"], + options.excludeRenovate || [ + "Renovate", + "https://github.com/apps/renovate", + ], + ], + "GitHub app", + ), + joinedMessage( + "populate the", + [ + (options.excludeAllContributors && options.excludeReleases) || [ + "ACCESS_TOKEN", + "a GitHub PAT with repo and workflow permissions", + ], + options.excludeReleases || [ + "NPM_TOKEN", + "an npm access token with automation permissions", + ], + ], + "secret", + ), + ].filter((line): line is string => !!line); + + switch (lines.length) { + case 0: + return []; + case 1: + return [{ label: `Be sure to ${lines[0]}` }]; + default: + return [ + { + label: "Be sure to:", + lines: lines.map((line) => `- ${line}`), + }, + ]; + } +} + +const itemBreak = "\n - "; + +function joinedMessage( + prefix: string, + entries: ([string, string] | true)[], + suffix: string, +) { + const realEntries = entries.filter( + (entry): entry is [string, string] => entry !== true, + ); + + switch (realEntries.length) { + case 0: + return undefined; + case 1: + return `${prefix} ${realEntries[0][0]} ${suffix} (${realEntries[0][1]}).`; + default: + return `${prefix} ${suffix}s: ${itemBreak}${realEntries + .map((entry) => `${entry[0]} (${entry[1]})`) + .join(itemBreak)}`; + } +} diff --git a/src/shared/getGitHubUserAsAllContributor.test.ts b/src/shared/getGitHubUserAsAllContributor.test.ts new file mode 100644 index 00000000..6a845f6d --- /dev/null +++ b/src/shared/getGitHubUserAsAllContributor.test.ts @@ -0,0 +1,98 @@ +import chalk from "chalk"; +import { SpyInstance, beforeEach, describe, expect, it, vi } from "vitest"; + +import { getGitHubUserAsAllContributor } from "./getGitHubUserAsAllContributor.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +let mockConsoleWarn: SpyInstance; + +const owner = "TestOwner"; + +describe("getGitHubUserAsAllContributor", () => { + beforeEach(() => { + mockConsoleWarn = vi + .spyOn(console, "warn") + .mockImplementation(() => undefined); + }); + + it("defaults to owner with a log when options.offline is true", async () => { + const actual = await getGitHubUserAsAllContributor({ + offline: true, + owner, + }); + + expect(actual).toEqual(owner); + expect(mockConsoleWarn).toHaveBeenCalledWith( + chalk.gray( + `Skipping populating all-contributors contributions for TestOwner because in --offline mode.`, + ), + ); + }); + + it("uses the user from gh api user when it succeeds", async () => { + const login = "gh-api-user"; + + mock$.mockResolvedValueOnce({ + stdout: JSON.stringify({ login }), + }); + + await getGitHubUserAsAllContributor({ owner }); + + expect(mockConsoleWarn).not.toHaveBeenCalled(); + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh api user", + ], + ], + [ + [ + "npx -y all-contributors-cli@6.25 add ", + " ", + "", + ], + "gh-api-user", + "code,content,doc,ideas,infra,maintenance,projectManagement,tool", + ], + ] + `); + }); + + it("defaults the user to the owner when gh api user fails", async () => { + mock$.mockRejectedValueOnce({}); + + await getGitHubUserAsAllContributor({ owner }); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + chalk.gray( + `Couldn't authenticate GitHub user, falling back to the provided owner name '${owner}'.`, + ), + ); + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh api user", + ], + ], + [ + [ + "npx -y all-contributors-cli@6.25 add ", + " ", + "", + ], + "TestOwner", + "code,content,doc,ideas,infra,maintenance,projectManagement,tool", + ], + ] + `); + }); +}); diff --git a/src/shared/getGitHubUserAsAllContributor.ts b/src/shared/getGitHubUserAsAllContributor.ts new file mode 100644 index 00000000..a290dded --- /dev/null +++ b/src/shared/getGitHubUserAsAllContributor.ts @@ -0,0 +1,48 @@ +import chalk from "chalk"; +import { $ } from "execa"; + +import { Options } from "./types.js"; + +interface GhUserOutput { + login: string; +} + +export async function getGitHubUserAsAllContributor( + options: Pick, +) { + if (options.offline) { + console.warn( + chalk.gray( + `Skipping populating all-contributors contributions for ${options.owner} because in --offline mode.`, + ), + ); + return options.owner; + } + + let user: string; + + try { + user = (JSON.parse((await $`gh api user`).stdout) as GhUserOutput).login; + } catch { + console.warn( + chalk.gray( + `Couldn't authenticate GitHub user, falling back to the provided owner name '${options.owner}'.`, + ), + ); + user = options.owner; + } + + const contributions = [ + "code", + "content", + "doc", + "ideas", + "infra", + "maintenance", + "projectManagement", + "tool", + ].join(","); + await $`npx -y all-contributors-cli@6.25 add ${user} ${contributions}`; + + return user; +} diff --git a/src/shared/options/args.ts b/src/shared/options/args.ts new file mode 100644 index 00000000..8d250589 --- /dev/null +++ b/src/shared/options/args.ts @@ -0,0 +1,46 @@ +import { ParseArgsConfig } from "node:util"; + +export const allArgOptions = { + access: { type: "string" }, + author: { type: "string" }, + base: { type: "string" }, + "create-repository": { type: "boolean" }, + description: { type: "string" }, + email: { type: "string" }, + "email-github": { type: "string" }, + "email-npm": { type: "string" }, + "exclude-all-contributors": { type: "boolean" }, + "exclude-compliance": { type: "boolean" }, + "exclude-lint-deprecation": { type: "boolean" }, + "exclude-lint-eslint": { type: "boolean" }, + "exclude-lint-jsdoc": { type: "boolean" }, + "exclude-lint-json": { type: "boolean" }, + "exclude-lint-knip": { type: "boolean" }, + "exclude-lint-md": { type: "boolean" }, + "exclude-lint-package-json": { type: "boolean" }, + "exclude-lint-packages": { type: "boolean" }, + "exclude-lint-perfectionist": { type: "boolean" }, + "exclude-lint-regex": { type: "boolean" }, + "exclude-lint-spelling": { type: "boolean" }, + "exclude-lint-strict": { type: "boolean" }, + "exclude-lint-stylistic": { type: "boolean" }, + "exclude-lint-yml": { type: "boolean" }, + "exclude-releases": { type: "boolean" }, + "exclude-renovate": { type: "boolean" }, + "exclude-tests": { type: "boolean" }, + funding: { type: "string" }, + keywords: { multiple: true, type: "string" }, + logo: { type: "string" }, + "logo-alt": { type: "string" }, + mode: { type: "string" }, + offline: { type: "boolean" }, + owner: { type: "string" }, + repository: { type: "string" }, + "skip-all-contributors-api": { type: "boolean" }, + "skip-github-api": { type: "boolean" }, + "skip-install": { type: "boolean" }, + "skip-removal": { type: "boolean" }, + "skip-restore": { type: "boolean" }, + "skip-uninstall": { type: "boolean" }, + title: { type: "string" }, +} as const satisfies ParseArgsConfig["options"]; diff --git a/src/shared/options/augmentOptionsWithExcludes.test.ts b/src/shared/options/augmentOptionsWithExcludes.test.ts new file mode 100644 index 00000000..a6306473 --- /dev/null +++ b/src/shared/options/augmentOptionsWithExcludes.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, it } from "vitest"; + +import { Options } from "../types.js"; +import { augmentOptionsWithExcludes } from "./augmentOptionsWithExcludes.js"; + +const optionsBase = { + access: "public", + author: undefined, + base: "everything", + createRepository: undefined, + description: "", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintDeprecation: undefined, + excludeLintESLint: undefined, + excludeLintJSDoc: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintRegex: undefined, + excludeLintSpelling: undefined, + excludeLintStrict: undefined, + excludeLintStylistic: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + logo: undefined, + mode: "create", + offline: true, + owner: "", + repository: "", + skipGitHubApi: false, + skipInstall: undefined, + skipRemoval: undefined, + skipRestore: undefined, + skipUninstall: undefined, + title: "", +} satisfies Options; + +describe("augmentOptionsWithExcludes", () => { + it("returns options without exclusions and skips prompting when exclusions are provided manually", async () => { + const options = { + ...optionsBase, + excludeCompliance: true, + } satisfies Options; + + const actual = await augmentOptionsWithExcludes(options); + + expect(actual).toBe(options); + }); + + it("uses the 'common' base without prompting when provided manually", async () => { + const options = { + ...optionsBase, + base: "common", + } satisfies Options; + + const actual = await augmentOptionsWithExcludes(options); + + expect(actual).toEqual({ + ...options, + excludeCompliance: true, + excludeLintDeprecation: true, + excludeLintESLint: true, + excludeLintJSDoc: true, + excludeLintJson: true, + excludeLintMd: true, + excludeLintPackageJson: true, + excludeLintPackages: true, + excludeLintPerfectionist: true, + excludeLintRegex: true, + excludeLintSpelling: true, + excludeLintStrict: true, + excludeLintStylistic: true, + excludeLintYml: true, + }); + }); + + it("uses the 'minimum' base without prompting when provided manually", async () => { + const options = { + ...optionsBase, + base: "minimum", + } satisfies Options; + + const actual = await augmentOptionsWithExcludes(options); + + expect(actual).toEqual({ + ...options, + excludeAllContributors: true, + excludeCompliance: true, + excludeLintDeprecation: true, + excludeLintESLint: true, + excludeLintJSDoc: true, + excludeLintJson: true, + excludeLintKnip: true, + excludeLintMd: true, + excludeLintPackageJson: true, + excludeLintPackages: true, + excludeLintPerfectionist: true, + excludeLintRegex: true, + excludeLintSpelling: true, + excludeLintStrict: true, + excludeLintStylistic: true, + excludeLintYml: true, + excludeReleases: true, + excludeRenovate: true, + excludeTests: true, + }); + }); + + it("uses the 'everything' base without prompting when provided manually", async () => { + const options = { + ...optionsBase, + base: "everything", + } satisfies Options; + + const actual = await augmentOptionsWithExcludes(options); + + expect(actual).toBe(options); + }); +}); diff --git a/src/shared/options/augmentOptionsWithExcludes.ts b/src/shared/options/augmentOptionsWithExcludes.ts new file mode 100644 index 00000000..366deb1f --- /dev/null +++ b/src/shared/options/augmentOptionsWithExcludes.ts @@ -0,0 +1,232 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; + +import { filterPromptCancel } from "../prompts.js"; +import { Options, OptionsBase } from "../types.js"; + +interface ExclusionDescription { + hint: string; + label: string; + uncommon?: true; +} + +type ExclusionKey = keyof Options & `exclude${string}`; + +const exclusionDescriptions: Record = { + excludeAllContributors: { + hint: "--exclude-all-contributors", + label: + "Add all-contributors to track contributions and display them in a README.md table.", + }, + excludeCompliance: { + hint: "--exclude-compliance", + label: + "Add a GitHub Actions workflow to verify that PRs match an expected format.", + uncommon: true, + }, + excludeLintDeprecation: { + hint: "--exclude-lint-deprecation", + label: + "Include an eslint-plugin-deprecation to reports on usage of code marked as @deprecated.", + uncommon: true, + }, + excludeLintESLint: { + hint: "--exclude-lint-eslint", + label: + "Include eslint-plugin-eslint-comment to enforce good practices around ESLint comment directives.", + uncommon: true, + }, + excludeLintJSDoc: { + hint: "--exclude-lint-jsdoc", + label: + "Include eslint-plugin-jsdoc to enforce good practices around JSDoc comments.", + uncommon: true, + }, + excludeLintJson: { + hint: "--exclude-lint-json", + label: "Apply linting and sorting to *.json and *.jsonc files.", + uncommon: true, + }, + excludeLintKnip: { + hint: "--exclude-lint-knip", + label: "Add Knip to detect unused files, dependencies, and code exports.", + }, + excludeLintMd: { + hint: "--exclude-lint-md", + label: "Apply linting to *.md files.", + uncommon: true, + }, + excludeLintPackageJson: { + hint: "--exclude-lint-package-json", + label: "Add npm-package-json-lint to lint for package.json correctness.", + uncommon: true, + }, + excludeLintPackages: { + hint: "--exclude-lint-packages", + label: + "Add a pnpm dedupe workflow to ensure packages aren't duplicated unnecessarily.", + uncommon: true, + }, + excludeLintPerfectionist: { + hint: "--exclude-lint-perfectionist", + label: + "Apply eslint-plugin-perfectionist to ensure imports, keys, and so on are in sorted order.", + uncommon: true, + }, + excludeLintRegex: { + hint: "--exclude-lint-regex", + label: + "Include eslint-plugin-regex to enforce good practices around regular expressions.", + uncommon: true, + }, + excludeLintSpelling: { + hint: "--exclude-lint-spelling", + label: "Add cspell to spell check against dictionaries of known words.", + uncommon: true, + }, + excludeLintStrict: { + hint: "--exclude-lint-strict", + label: + "Include strict logical lint rules such as typescript-eslint's strict config. ", + uncommon: true, + }, + excludeLintStylistic: { + hint: "--exclude-lint-stylistic", + label: + "Include stylistic lint rules such as typescript-eslint's stylistic config.", + uncommon: true, + }, + excludeLintYml: { + hint: "--exclude-lint-yml", + label: "Apply linting and sorting to *.yaml and *.yml files.", + uncommon: true, + }, + excludeReleases: { + hint: "--exclude-releases", + label: + "Add release-it to generate changelogs, package bumps, and publishes based on conventional commits.", + }, + excludeRenovate: { + hint: "--exclude-renovate", + label: "Add a Renovate config to keep dependencies up-to-date with PRs.", + }, + excludeTests: { + hint: "--exclude-tests", + label: + "Add Vitest tooling for fast unit tests, configured with coverage tracking.", + }, +}; + +const exclusionKeys = Object.keys(exclusionDescriptions) as ExclusionKey[]; + +export async function augmentOptionsWithExcludes( + options: Options, +): Promise { + if ( + Object.keys(options).some( + (key) => + key in exclusionDescriptions && + options[key as keyof typeof options] !== undefined, + ) + ) { + return options; + } + + const base = + options.base ?? + filterPromptCancel( + await prompts.select({ + initialValue: "common" as OptionsBase, + message: `How much tooling would you like the template to set up for you?`, + options: [ + { + label: makeLabel( + "everything", + "The most comprehensive tooling imaginable: sorting, spellchecking, and more!", + ), + value: "everything", + }, + { + hint: "recommended", + label: makeLabel( + "common", + "Bare starters plus testing and automation for all-contributors and releases.", + ), + value: "common", + }, + { + label: makeLabel( + "minimum", + "Just bare starter tooling: building, formatting, linting, and type checking.", + ), + value: "minimum", + }, + { + label: makeLabel("prompt", "(allow me to customize)"), + value: "prompt", + }, + ], + }), + ); + + switch (base) { + case undefined: + return undefined; + + case "common": + return { + ...options, + ...Object.fromEntries( + exclusionKeys + .filter((exclusion) => exclusionDescriptions[exclusion].uncommon) + .map((exclusion) => [exclusion, options[exclusion] ?? true]), + ), + }; + + case "everything": + return options; + + case "minimum": + return { + ...options, + ...Object.fromEntries( + exclusionKeys.map((exclusion) => [ + exclusion, + options[exclusion] ?? true, + ]), + ), + }; + + case "prompt": + const exclusionsNotEnabled = new Set( + filterPromptCancel( + await prompts.multiselect({ + initialValues: exclusionKeys, + message: + "Select the tooling portions you'd like to remove. All are enabled by default.", + options: Object.entries(exclusionDescriptions).map( + ([value, { hint, label }]) => ({ + hint, + label, + value: value as ExclusionKey, + }), + ), + }), + ), + ); + + return { + ...options, + ...Object.fromEntries( + exclusionKeys.map( + (exclusionKey) => + [exclusionKey, !exclusionsNotEnabled.has(exclusionKey)] as const, + ), + ), + }; + } +} + +function makeLabel(label: string, message: string) { + return [chalk.bold(label), message].join("\t "); +} diff --git a/src/shared/options/detectEmailRedundancy.test.ts b/src/shared/options/detectEmailRedundancy.test.ts new file mode 100644 index 00000000..3091a609 --- /dev/null +++ b/src/shared/options/detectEmailRedundancy.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; + +import { detectEmailRedundancy } from "./detectEmailRedundancy.js"; + +describe("detectEmailRedundancy", () => { + it("returns undefined when only email is specified", () => { + expect(detectEmailRedundancy({ email: "test@email.com" })).toBeUndefined(); + }); + + it("returns undefined when email-github and email-npm are specified while email is not", () => { + expect( + detectEmailRedundancy({ + "email-github": "test@email.com", + "email-npm": "test@email.com", + }), + ).toBeUndefined(); + }); + + it("returns a complaint when email-github is specified while email and email-npm are not", () => { + expect( + detectEmailRedundancy({ + "email-github": "test@email.com", + }), + ).toBe( + "If --email-github is specified, either --email or --email-npm should be.", + ); + }); + + it("returns a complaint when email-npm is specified while email and email-github are not", () => { + expect( + detectEmailRedundancy({ + "email-npm": "test@email.com", + }), + ).toBe( + "If --email-npm is specified, either --email or --email-github should be.", + ); + }); +}); diff --git a/src/shared/options/detectEmailRedundancy.ts b/src/shared/options/detectEmailRedundancy.ts new file mode 100644 index 00000000..15044b47 --- /dev/null +++ b/src/shared/options/detectEmailRedundancy.ts @@ -0,0 +1,23 @@ +export interface EmailValues { + email?: boolean | string; + "email-github"?: boolean | string; + "email-npm"?: boolean | string; +} + +export function detectEmailRedundancy(values: EmailValues) { + if (values.email) { + return values["email-github"] && values["email-npm"] + ? "--email should not be specified if both --email-github and --email-npm are specified." + : undefined; + } + + if (values["email-github"] && !values["email-npm"]) { + return "If --email-github is specified, either --email or --email-npm should be."; + } + + if (values["email-npm"] && !values["email-github"]) { + return "If --email-npm is specified, either --email or --email-github should be."; + } + + return undefined; +} diff --git a/src/shared/options/ensureRepositoryExists.test.ts b/src/shared/options/ensureRepositoryExists.test.ts new file mode 100644 index 00000000..299435fd --- /dev/null +++ b/src/shared/options/ensureRepositoryExists.test.ts @@ -0,0 +1,186 @@ +import { Octokit } from "octokit"; +import { describe, expect, it, vi } from "vitest"; + +import { ensureRepositoryExists } from "./ensureRepositoryExists.js"; + +const mockSelect = vi.fn(); +const mockText = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + intro: vi.fn(), + isCancel: vi.fn(), + outro: vi.fn(), + get select() { + return mockSelect; + }, + get text() { + return mockText; + }, +})); + +const mockDoesRepositoryExist = vi.fn(); + +vi.mock("../doesRepositoryExist.js", () => ({ + get doesRepositoryExist() { + return mockDoesRepositoryExist; + }, +})); + +const auth = "abc123"; +const owner = "StubOwner"; +const repository = "stub-repository"; + +const createUsingTemplate = vi.fn(); + +const createMockOctokit = () => + ({ rest: { repos: { createUsingTemplate } } }) as unknown as Octokit; + +describe("ensureRepositoryExists", () => { + it("returns the repository when octokit is undefined", async () => { + const actual = await ensureRepositoryExists(undefined, { + createRepository: false, + owner, + repository, + }); + + expect(actual).toEqual({ octokit: undefined, repository }); + }); + + it("returns the repository when octokit is defined and the repository exists", async () => { + mockDoesRepositoryExist.mockResolvedValue(true); + const octokit = createMockOctokit(); + const actual = await ensureRepositoryExists( + { auth, octokit }, + { + createRepository: false, + owner, + repository, + }, + ); + + expect(actual).toEqual({ github: { auth, octokit }, repository }); + }); + + it("creates a new repository when createRepository is true and the repository does not exist", async () => { + const octokit = createMockOctokit(); + + mockDoesRepositoryExist.mockResolvedValue(false); + + const actual = await ensureRepositoryExists( + { auth, octokit }, + { + createRepository: true, + owner, + repository, + }, + ); + + expect(actual).toEqual({ github: { auth, octokit }, repository }); + expect(octokit.rest.repos.createUsingTemplate).toHaveBeenCalledWith({ + name: repository, + owner, + template_owner: "JoshuaKGoldberg", + template_repo: "create-typescript-app", + }); + }); + + it("creates a new repository when the prompt is 'create' and the repository does not exist", async () => { + const octokit = createMockOctokit(); + + mockDoesRepositoryExist.mockResolvedValue(false); + mockSelect.mockResolvedValue("create"); + + const actual = await ensureRepositoryExists( + { auth, octokit }, + { + createRepository: false, + owner, + repository, + }, + ); + + expect(actual).toEqual({ github: { auth, octokit }, repository }); + expect(octokit.rest.repos.createUsingTemplate).toHaveBeenCalledWith({ + name: repository, + owner, + template_owner: "JoshuaKGoldberg", + template_repo: "create-typescript-app", + }); + }); + + it("returns the second repository when the prompt is 'different', the first repository does not exist, and the second repository exists", async () => { + const octokit = createMockOctokit(); + const newRepository = "new-repository"; + + mockDoesRepositoryExist + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(true); + mockSelect.mockResolvedValueOnce("different"); + mockText.mockResolvedValue(newRepository); + + const actual = await ensureRepositoryExists( + { auth, octokit }, + { + createRepository: false, + owner, + repository, + }, + ); + + expect(actual).toEqual({ + github: { auth, octokit }, + repository: newRepository, + }); + expect(octokit.rest.repos.createUsingTemplate).not.toHaveBeenCalled(); + }); + + it("creates the second repository when the prompt is 'different', the first repository does not exist, and the second repository does not exist", async () => { + const octokit = createMockOctokit(); + const newRepository = "new-repository"; + + mockDoesRepositoryExist.mockResolvedValue(false); + mockSelect + .mockResolvedValueOnce("different") + .mockResolvedValueOnce("create"); + mockText.mockResolvedValue(newRepository); + + const actual = await ensureRepositoryExists( + { auth, octokit }, + { + createRepository: false, + owner, + repository, + }, + ); + + expect(actual).toEqual({ + github: { auth, octokit }, + repository: newRepository, + }); + expect(octokit.rest.repos.createUsingTemplate).toHaveBeenCalledWith({ + name: newRepository, + owner, + template_owner: "JoshuaKGoldberg", + template_repo: "create-typescript-app", + }); + }); + + it("switches octokit to undefined when the prompt is 'local' and the repository does not exist", async () => { + const octokit = createMockOctokit(); + + mockDoesRepositoryExist.mockResolvedValue(false); + mockSelect.mockResolvedValue("local"); + + const actual = await ensureRepositoryExists( + { auth, octokit }, + { + createRepository: false, + owner, + repository, + }, + ); + + expect(actual).toEqual({ octokit: undefined, repository }); + expect(octokit.rest.repos.createUsingTemplate).not.toHaveBeenCalled(); + }); +}); diff --git a/src/shared/options/ensureRepositoryExists.ts b/src/shared/options/ensureRepositoryExists.ts new file mode 100644 index 00000000..22856d8b --- /dev/null +++ b/src/shared/options/ensureRepositoryExists.ts @@ -0,0 +1,89 @@ +import * as prompts from "@clack/prompts"; + +import { doesRepositoryExist } from "../doesRepositoryExist.js"; +import { filterPromptCancel } from "../prompts.js"; +import { Options } from "../types.js"; +import { GitHub } from "./getGitHub.js"; + +export type EnsureRepositoryExistsOptions = Pick< + Options, + "createRepository" | "owner" | "repository" +>; + +export interface RepositoryExistsResult { + github: GitHub | undefined; + repository: string; +} + +export async function ensureRepositoryExists( + github: GitHub | undefined, + options: EnsureRepositoryExistsOptions, +): Promise> { + // We'll only respect input options once before prompting for them + let { createRepository, repository } = options; + + // We'll continuously pester the user for a repository + // until they bail, create a new one, or it exists. + while (github) { + if (await doesRepositoryExist(github.octokit, options)) { + return { github, repository }; + } + + const selection = createRepository + ? "create" + : filterPromptCancel( + (await prompts.select({ + message: `Repository ${options.repository} doesn't seem to exist under ${options.owner}. What would you like to do?`, + options: [ + { label: "Create a new repository", value: "create" }, + { + label: "Switch to a different repository name", + value: "different", + }, + { + label: "Keep changes local", + value: "local", + }, + { label: "Bail out and maybe try again later", value: "bail" }, + ], + })) as "bail" | "create" | "different" | "local", + ); + + createRepository = false; + + switch (selection) { + case undefined: + case "bail": + return {}; + + case "create": + await github.octokit.rest.repos.createUsingTemplate({ + name: repository, + owner: options.owner, + template_owner: "JoshuaKGoldberg", + template_repo: "create-typescript-app", + }); + return { github: github, repository }; + + case "different": + const newRepository = filterPromptCancel( + await prompts.text({ + message: `What would you like to call the repository?`, + }), + ); + + if (!newRepository) { + return {}; + } + + repository = newRepository; + break; + + case "local": + github = undefined; + break; + } + } + + return { github: github, repository }; +} diff --git a/src/shared/options/getGitHub.test.ts b/src/shared/options/getGitHub.test.ts new file mode 100644 index 00000000..e7d3bdce --- /dev/null +++ b/src/shared/options/getGitHub.test.ts @@ -0,0 +1,33 @@ +import { Octokit } from "octokit"; +import { describe, expect, it, vi } from "vitest"; + +import { getGitHub } from "./getGitHub.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +vi.mock("octokit"); + +describe("getOctokit", () => { + it("throws an error when gh auth status fails", async () => { + mock$.mockRejectedValueOnce(new Error("Oh no!")); + + await expect(getGitHub).rejects.toMatchInlineSnapshot( + "[Error: GitHub authentication failed.]", + ); + }); + + it("returns a new Octokit when gh auth status succeeds", async () => { + const auth = "abc123"; + mock$.mockResolvedValueOnce({}).mockResolvedValueOnce({ stdout: auth }); + + const actual = await getGitHub(); + + expect(actual).toEqual({ auth, octokit: new Octokit({ auth }) }); + }); +}); diff --git a/src/shared/options/getGitHub.ts b/src/shared/options/getGitHub.ts new file mode 100644 index 00000000..13336901 --- /dev/null +++ b/src/shared/options/getGitHub.ts @@ -0,0 +1,22 @@ +import { $ } from "execa"; +import { Octokit } from "octokit"; + +export interface GitHub { + auth: string; + octokit: Octokit; +} + +export async function getGitHub(): Promise { + try { + await $`gh auth status`; + } catch (error) { + throw new Error("GitHub authentication failed.", { + cause: (error as Error).message, + }); + } + + const auth = (await $`gh auth token`).stdout.trim(); + const octokit = new Octokit({ auth }); + + return { auth, octokit }; +} diff --git a/src/shared/options/getPrefillOrPromptedOption.test.ts b/src/shared/options/getPrefillOrPromptedOption.test.ts new file mode 100644 index 00000000..75906cd2 --- /dev/null +++ b/src/shared/options/getPrefillOrPromptedOption.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it, vi } from "vitest"; + +import { getPrefillOrPromptedOption } from "./getPrefillOrPromptedOption.js"; + +const mockText = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + isCancel: () => false, + get text() { + return mockText; + }, +})); + +describe("getPrefillOrPromptedValue", () => { + it("logs a pre-fill message when a first value already exists", async () => { + const existing = "existing value"; + + const actual = await getPrefillOrPromptedOption(existing, ""); + + expect(actual).toEqual(existing); + }); + + it("prompts for a new value when the value doesn't already exist", async () => { + const expected = "expected value"; + + mockText.mockResolvedValue(expected); + + const actual = await getPrefillOrPromptedOption(undefined, ""); + + expect(actual).toEqual(expected); + }); + + it("provides no placeholder when one is not provided", async () => { + const message = "Test message"; + + await getPrefillOrPromptedOption(undefined, message); + + expect(mockText).toHaveBeenCalledWith({ + message, + placeholder: undefined, + validate: expect.any(Function), + }); + }); + + it("provides the placeholder's awaited return when a placeholder function is provided", async () => { + const message = "Test message"; + const placeholder = "Test placeholder"; + + await getPrefillOrPromptedOption( + undefined, + message, + vi.fn().mockResolvedValue(placeholder), + ); + + expect(mockText).toHaveBeenCalledWith({ + message, + placeholder, + validate: expect.any(Function), + }); + }); +}); diff --git a/src/shared/options/getPrefillOrPromptedOption.ts b/src/shared/options/getPrefillOrPromptedOption.ts new file mode 100644 index 00000000..f79bb5eb --- /dev/null +++ b/src/shared/options/getPrefillOrPromptedOption.ts @@ -0,0 +1,25 @@ +import * as prompts from "@clack/prompts"; + +import { filterPromptCancel } from "../prompts.js"; + +export async function getPrefillOrPromptedOption( + existingValue: string | undefined, + message: string, + getPlaceholder?: () => Promise, +) { + if (existingValue) { + return existingValue; + } + + return filterPromptCancel( + await prompts.text({ + message, + placeholder: await getPlaceholder?.(), + validate: (val) => { + if (val.length === 0) { + return "Please enter a value."; + } + }, + }), + ); +} diff --git a/src/shared/options/optionsSchema.ts b/src/shared/options/optionsSchema.ts new file mode 100644 index 00000000..44309ee9 --- /dev/null +++ b/src/shared/options/optionsSchema.ts @@ -0,0 +1,60 @@ +import { z } from "zod"; + +export const optionsSchemaShape = { + access: z.union([z.literal("public"), z.literal("restricted")]).optional(), + author: z.string().optional(), + base: z + .union([ + z.literal("common"), + z.literal("everything"), + z.literal("minimum"), + z.literal("prompt"), + ]) + .optional(), + createRepository: z.boolean().optional(), + description: z.string().optional(), + email: z + .object({ + github: z.string().email(), + npm: z.string().email(), + }) + .optional(), + excludeAllContributors: z.boolean().optional(), + excludeCompliance: z.boolean().optional(), + excludeLintDeprecation: z.boolean().optional(), + excludeLintESLint: z.boolean().optional(), + excludeLintJSDoc: z.boolean().optional(), + excludeLintJson: z.boolean().optional(), + excludeLintKnip: z.boolean().optional(), + excludeLintMd: z.boolean().optional(), + excludeLintPackageJson: z.boolean().optional(), + excludeLintPackages: z.boolean().optional(), + excludeLintPerfectionist: z.boolean().optional(), + excludeLintRegex: z.boolean().optional(), + excludeLintSpelling: z.boolean().optional(), + excludeLintStrict: z.boolean().optional(), + excludeLintStylistic: z.boolean().optional(), + excludeLintYml: z.boolean().optional(), + excludeReleases: z.boolean().optional(), + excludeRenovate: z.boolean().optional(), + excludeTests: z.boolean().optional(), + funding: z.string().optional(), + keywords: z.array(z.string()).optional(), + logo: z.string().optional(), + logoAlt: z.string().optional(), + mode: z + .union([z.literal("create"), z.literal("initialize"), z.literal("migrate")]) + .optional(), + offline: z.boolean().optional(), + owner: z.string().optional(), + repository: z.string().optional(), + skipAllContributorsApi: z.boolean().optional(), + skipGitHubApi: z.boolean().optional(), + skipInstall: z.boolean().optional(), + skipRemoval: z.boolean().optional(), + skipRestore: z.boolean().optional(), + skipUninstall: z.boolean().optional(), + title: z.string().optional(), +}; + +export const optionsSchema = z.object(optionsSchemaShape); diff --git a/src/shared/options/readOptionDefaults/index.test.ts b/src/shared/options/readOptionDefaults/index.test.ts new file mode 100644 index 00000000..586209bb --- /dev/null +++ b/src/shared/options/readOptionDefaults/index.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it, vi } from "vitest"; + +import { readOptionDefaults } from "./index.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const mockNpmUser = vi.fn(); + +vi.mock("npm-user", () => ({ + get default() { + return mockNpmUser; + }, +})); + +const mockReadPackageData = vi.fn(); + +vi.mock("../../packages.js", () => ({ + get readPackageData() { + return mockReadPackageData; + }, +})); + +describe("readOptionDefaults", () => { + describe("email", () => { + it("returns the npm whoami email from npm when only an npm exists", async () => { + mock$.mockImplementation(([command]: string[]) => + command === "npm whoami" ? { stdout: "npm-username" } : undefined, + ); + mockNpmUser.mockImplementation((username: string) => ({ + email: `test@${username}.com`, + })); + + const actual = await readOptionDefaults().email(); + + expect(actual).toEqual({ + github: "test@npm-username.com", + npm: "test@npm-username.com", + }); + }); + + it("returns the npm whoami email from npm when only a package author email exists", async () => { + mock$.mockResolvedValue({ stdout: "" }); + mockReadPackageData.mockResolvedValue({ + author: { + email: "test@package.com", + }, + }); + + const actual = await readOptionDefaults().email(); + + expect(actual).toEqual({ + github: "test@package.com", + npm: "test@package.com", + }); + }); + + it("returns the git user email when only a git user email exists", async () => { + mock$.mockImplementation(([command]: string[]) => + command === "git config --get user.email" + ? { stdout: "test@git.com" } + : undefined, + ); + mockReadPackageData.mockResolvedValue({}); + + const actual = await readOptionDefaults().email(); + + expect(actual).toEqual({ + github: "test@git.com", + npm: "test@git.com", + }); + }); + + it("returns both the git user email and the npm user email when both exist", async () => { + mock$.mockImplementation(([command]: string[]) => ({ + stdout: + command === "git config --get user.email" + ? "test@git.com" + : "npm-username", + })); + mockReadPackageData.mockResolvedValue({}); + + const actual = await readOptionDefaults().email(); + + expect(actual).toEqual({ + github: "test@git.com", + npm: "test@npm-username.com", + }); + }); + + it("returns undefined when neither git nor npm emails exist", async () => { + mock$.mockResolvedValue({ stdout: "" }); + mockReadPackageData.mockResolvedValue({}); + + const actual = await readOptionDefaults().email(); + + expect(actual).toBeUndefined(); + }); + }); +}); diff --git a/src/shared/options/readOptionDefaults/index.ts b/src/shared/options/readOptionDefaults/index.ts new file mode 100644 index 00000000..af172899 --- /dev/null +++ b/src/shared/options/readOptionDefaults/index.ts @@ -0,0 +1,59 @@ +import { $ } from "execa"; +import gitRemoteOriginUrl from "git-remote-origin-url"; +import gitUrlParse from "git-url-parse"; +import lazyValue from "lazy-value"; +import fs from "node:fs/promises"; +import npmUser from "npm-user"; + +import { readPackageData } from "../../packages.js"; +import { tryCatchAsync } from "../../tryCatchAsync.js"; +import { tryCatchLazyValueAsync } from "../../tryCatchLazyValueAsync.js"; +import { parsePackageAuthor } from "./parsePackageAuthor.js"; +import { readDefaultsFromReadme } from "./readDefaultsFromReadme.js"; + +export function readOptionDefaults() { + const gitDefaults = tryCatchLazyValueAsync(async () => + gitUrlParse(await gitRemoteOriginUrl()), + ); + + const npmDefaults = tryCatchLazyValueAsync(async () => { + const whoami = (await $`npm whoami`).stdout; + return whoami ? await npmUser(whoami) : undefined; + }); + + const packageData = lazyValue(readPackageData); + const packageAuthor = lazyValue(async () => + parsePackageAuthor(await packageData()), + ); + + return { + author: async () => (await packageAuthor()).author ?? npmDefaults.name, + description: async () => (await packageData()).description, + email: async () => { + const gitEmail = await tryCatchAsync( + async () => (await $`git config --get user.email`).stdout, + ); + const npmEmail = + (await npmDefaults())?.email ?? (await packageAuthor()).email; + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + return gitEmail || npmEmail + ? { github: (gitEmail || npmEmail)!, npm: (npmEmail || gitEmail)! } + : undefined; + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + }, + funding: async () => + await tryCatchAsync( + async () => + (await fs.readFile(".github/FUNDING.yml")) + .toString() + .split(":")[1] + ?.trim(), + ), + owner: async () => + (await gitDefaults())?.organization ?? (await packageAuthor()).author, + repository: async () => + (await gitDefaults())?.name ?? (await packageData()).name, + ...readDefaultsFromReadme(), + }; +} diff --git a/src/shared/options/readOptionDefaults/parsePackageAuthor.test.ts b/src/shared/options/readOptionDefaults/parsePackageAuthor.test.ts new file mode 100644 index 00000000..078768d1 --- /dev/null +++ b/src/shared/options/readOptionDefaults/parsePackageAuthor.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it, vi } from "vitest"; + +import { parsePackageAuthor } from "./parsePackageAuthor.js"; + +const mockReadFileSafe = vi.fn(); + +vi.mock("../../shared/readFileSafe.js", () => ({ + get readFileSafe() { + return mockReadFileSafe; + }, +})); + +describe("parsePackageAuthor", () => { + it.each([ + [{}, {}], + [{ author: "abc123" }, { author: "abc123" }], + [ + { author: "abc123", email: "def@ghi.com" }, + { author: "abc123 " }, + ], + [ + { author: "abc123", email: "def@ghi.com" }, + { author: "abc123 " }, + ], + [ + { author: "abc123", email: "def@ghi.com" }, + { author: { email: "def@ghi.com", name: "abc123" } }, + ], + ])("returns %s when given %s", (expected, packageData) => { + expect(parsePackageAuthor(packageData)).toEqual(expected); + }); +}); diff --git a/src/shared/options/readOptionDefaults/parsePackageAuthor.ts b/src/shared/options/readOptionDefaults/parsePackageAuthor.ts new file mode 100644 index 00000000..88c27107 --- /dev/null +++ b/src/shared/options/readOptionDefaults/parsePackageAuthor.ts @@ -0,0 +1,16 @@ +import { PartialPackageData } from "../../types.js"; + +export function parsePackageAuthor(packageData: PartialPackageData) { + const [packageAuthor, packageEmail] = + typeof packageData.author === "string" + ? [ + packageData.author.split("<")[0].trim(), + packageData.author.split(/<|>/)[1]?.trim(), + ] + : [packageData.author?.name, packageData.author?.email]; + + return { + author: packageAuthor, + email: packageEmail, + }; +} diff --git a/src/shared/options/readOptionDefaults/readDefaultsFromReadme.test.ts b/src/shared/options/readOptionDefaults/readDefaultsFromReadme.test.ts new file mode 100644 index 00000000..ae708bce --- /dev/null +++ b/src/shared/options/readOptionDefaults/readDefaultsFromReadme.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it, vi } from "vitest"; + +import { readDefaultsFromReadme } from "./readDefaultsFromReadme.js"; + +const mockReadFileSafe = vi.fn(); + +vi.mock("../../readFileSafe.js", () => ({ + get readFileSafe() { + return mockReadFileSafe; + }, +})); + +describe("readDefaultsFromReadme", () => { + describe("logo", () => { + it("defaults to undefined when it cannot be found", async () => { + mockReadFileSafe.mockResolvedValue("nothing."); + + const logo = await readDefaultsFromReadme().logo(); + + expect(logo).toBeUndefined(); + }); + + it("parses when found in an unquoted string", async () => { + mockReadFileSafe.mockResolvedValue(""); + + const logo = await readDefaultsFromReadme().logo(); + + expect(logo).toBe("abc/def.jpg"); + }); + + it("parses when found in a single quoted string", async () => { + mockReadFileSafe.mockResolvedValue(""); + + const logo = await readDefaultsFromReadme().logo(); + + expect(logo).toBe("abc/def.jpg"); + }); + + it("parses when found in a double quoted string", async () => { + mockReadFileSafe.mockResolvedValue(''); + + const logo = await readDefaultsFromReadme().logo(); + + expect(logo).toBe("abc/def.jpg"); + }); + }); + + describe("title", () => { + it("defaults to undefined when it cannot be found", async () => { + mockReadFileSafe.mockResolvedValue("nothing."); + + const title = await readDefaultsFromReadme().title(); + + expect(title).toBeUndefined(); + }); + + it('reads title as markdown from "README.md" when it exists', async () => { + mockReadFileSafe.mockResolvedValue("# My Awesome Package"); + + const title = await readDefaultsFromReadme().title(); + + expect(title).toBe("My Awesome Package"); + }); + + it('reads title as HTML from "README.md" when it exists', async () => { + mockReadFileSafe.mockResolvedValue( + '

My Awesome Package

', + ); + + const title = await readDefaultsFromReadme().title(); + + expect(title).toBe("My Awesome Package"); + }); + + it("returns undefined when title does not exist", async () => { + mockReadFileSafe.mockResolvedValue(""); + + const title = await readDefaultsFromReadme().title(); + + expect(title).toBeUndefined(); + }); + }); +}); diff --git a/src/shared/options/readOptionDefaults/readDefaultsFromReadme.ts b/src/shared/options/readOptionDefaults/readDefaultsFromReadme.ts new file mode 100644 index 00000000..27b339df --- /dev/null +++ b/src/shared/options/readOptionDefaults/readDefaultsFromReadme.ts @@ -0,0 +1,23 @@ +import lazyValue from "lazy-value"; + +import { readFileSafe } from "../../readFileSafe.js"; + +export function readDefaultsFromReadme() { + const readme = lazyValue(async () => await readFileSafe("README.md", "")); + + const imageTag = lazyValue( + async () => (await readme()).match(//)?.[0], + ); + + return { + logo: async () => + (await imageTag()) + ?.match(/src\s*=(.+)?\/>/)?.[1] + ?.replaceAll(/^['"]|['"]$/g, ""), + title: async () => + (await readme()) + .match(/^(?:# |)(.*?)(?:<\/h1>)?$/i)?.[1] + ?.trim() + ?.replace(/<[^>]+(?:>|$)/g, ""), + }; +} diff --git a/src/shared/options/readOptions.test.ts b/src/shared/options/readOptions.test.ts new file mode 100644 index 00000000..6b35e82b --- /dev/null +++ b/src/shared/options/readOptions.test.ts @@ -0,0 +1,358 @@ +import { describe, expect, it, vi } from "vitest"; +import z from "zod"; + +import { Options } from "../types.js"; +import { optionsSchemaShape } from "./optionsSchema.js"; +import { readOptions } from "./readOptions.js"; + +const emptyOptions = { + access: undefined, + author: undefined, + base: undefined, + createRepository: undefined, + description: undefined, + email: undefined, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintDeprecation: undefined, + excludeLintESLint: undefined, + excludeLintJSDoc: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintRegex: undefined, + excludeLintSpelling: undefined, + excludeLintStrict: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + offline: undefined, + owner: undefined, + repository: undefined, + skipAllContributorsApi: undefined, + skipGitHubApi: undefined, + skipInstall: undefined, + skipRemoval: undefined, + skipRestore: undefined, + skipUninstall: undefined, + title: undefined, +}; + +const mockOptions = { + base: "prompt", + github: "mock.git", + repository: "mock.repository", +}; + +vi.mock("../cli/spinners.ts", () => ({ + withSpinner() { + return () => ({}); + }, +})); + +const mockAugmentOptionsWithExcludes = vi.fn(); + +vi.mock("./augmentOptionsWithExcludes.js", () => ({ + get augmentOptionsWithExcludes() { + return mockAugmentOptionsWithExcludes; + }, +})); + +const mockDetectEmailRedundancy = vi.fn(); + +vi.mock("./detectEmailRedundancy.js", () => ({ + get detectEmailRedundancy() { + return mockDetectEmailRedundancy; + }, +})); + +const mockGetPrefillOrPromptedOption = vi.fn(); + +vi.mock("./getPrefillOrPromptedOption.js", () => ({ + get getPrefillOrPromptedOption() { + return mockGetPrefillOrPromptedOption; + }, +})); + +const mockEnsureRepositoryExists = vi.fn(); + +vi.mock("./ensureRepositoryExists.js", () => ({ + get ensureRepositoryExists() { + return mockEnsureRepositoryExists; + }, +})); + +vi.mock("./getGitHub.js", () => ({ + getGitHub() { + return undefined; + }, +})); + +vi.mock("./readOptionDefaults/index.js", () => ({ + readOptionDefaults() { + return { + author: vi.fn(), + description: vi.fn(), + email: vi.fn(), + funding: vi.fn(), + logo: vi.fn(), + owner: vi.fn(), + repository: vi.fn(), + title: vi.fn(), + }; + }, +})); + +describe("readOptions", () => { + it("returns a cancellation when an arg is invalid", async () => { + const validationResult = z + .object({ base: optionsSchemaShape.base }) + .safeParse({ base: "b" }); + + const actual = await readOptions(["--base", "b"], "create"); + + expect(actual).toStrictEqual({ + cancelled: true, + error: (validationResult as z.SafeParseError<{ base: string }>).error, + options: { ...emptyOptions, base: "b" }, + }); + }); + + it("returns a cancellation when an email redundancy is detected", async () => { + const error = "Too many emails!"; + mockDetectEmailRedundancy.mockReturnValue(error); + mockGetPrefillOrPromptedOption.mockImplementation(() => undefined); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + error, + options: { + ...emptyOptions, + }, + }); + }); + + it("returns a cancellation when the owner prompt is cancelled", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption.mockImplementation(() => undefined); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + }, + }); + }); + + it("returns a cancellation when the repository prompt is cancelled", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementation(() => undefined); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + owner: "MockOwner", + }, + }); + }); + + it("returns a cancellation when ensureRepositoryPrompt does not return a repository", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementationOnce(() => "MockRepository") + .mockImplementation(() => undefined); + mockEnsureRepositoryExists.mockResolvedValue({}); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + owner: "MockOwner", + repository: "MockRepository", + }, + }); + }); + + it("returns a cancellation when the description prompt is cancelled", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementationOnce(() => "MockRepository") + .mockImplementation(() => undefined); + mockEnsureRepositoryExists.mockResolvedValue({ + github: mockOptions.github, + repository: mockOptions.repository, + }); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + owner: "MockOwner", + repository: "MockRepository", + }, + }); + }); + + it("returns a cancellation when the title prompt is cancelled", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementationOnce(() => "MockRepository") + .mockImplementationOnce(() => "Mock description.") + .mockImplementation(() => undefined); + mockEnsureRepositoryExists.mockResolvedValue({ + github: mockOptions.github, + repository: mockOptions.repository, + }); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + description: "Mock description.", + owner: "MockOwner", + repository: "MockRepository", + }, + }); + }); + + it("returns a cancellation when the logo alt prompt is cancelled", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementationOnce(() => "MockRepository") + .mockImplementationOnce(() => "Mock description.") + .mockImplementation(() => undefined); + mockEnsureRepositoryExists.mockResolvedValue({ + github: mockOptions.github, + repository: mockOptions.repository, + }); + + expect(await readOptions(["--logo", "logo.svg"], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + description: "Mock description.", + owner: "MockOwner", + repository: "MockRepository", + }, + }); + }); + + it("returns a cancellation when the email prompt is cancelled", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementationOnce(() => "MockRepository") + .mockImplementationOnce(() => "Mock description.") + .mockImplementationOnce(() => "Mock title.") + .mockImplementation(() => undefined); + mockEnsureRepositoryExists.mockResolvedValue({ + github: mockOptions.github, + repository: mockOptions.repository, + }); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + description: "Mock description.", + owner: "MockOwner", + repository: "MockRepository", + title: "Mock title.", + }, + }); + }); + + it("returns a cancellation when augmentOptionsWithExcludes returns undefined", async () => { + mockDetectEmailRedundancy.mockReturnValue(false); + mockGetPrefillOrPromptedOption + .mockImplementationOnce(() => "MockOwner") + .mockImplementationOnce(() => "MockRepository") + .mockImplementationOnce(() => "Mock description.") + .mockImplementationOnce(() => "Mock title.") + .mockImplementation(() => undefined); + mockEnsureRepositoryExists.mockResolvedValue({ + github: mockOptions.github, + repository: mockOptions.repository, + }); + mockAugmentOptionsWithExcludes.mockResolvedValue(undefined); + + expect(await readOptions([], "create")).toStrictEqual({ + cancelled: true, + options: { + ...emptyOptions, + description: "Mock description.", + owner: "MockOwner", + repository: "MockRepository", + title: "Mock title.", + }, + }); + }); + + it("returns success options when --base is valid", async () => { + mockAugmentOptionsWithExcludes.mockResolvedValue({ + ...emptyOptions, + ...mockOptions, + }); + mockGetPrefillOrPromptedOption.mockImplementation(() => "mock"); + + expect( + await readOptions(["--base", mockOptions.base], "create"), + ).toStrictEqual({ + cancelled: false, + github: mockOptions.github, + options: { + ...emptyOptions, + ...mockOptions, + }, + }); + }); + + it("skips API calls when --offline is true", async () => { + mockAugmentOptionsWithExcludes.mockImplementation((options: Options) => ({ + ...emptyOptions, + ...mockOptions, + ...options, + })); + mockGetPrefillOrPromptedOption.mockImplementation(() => "mock"); + mockEnsureRepositoryExists.mockResolvedValue({ + github: mockOptions.github, + repository: mockOptions.repository, + }); + + expect( + await readOptions(["--base", mockOptions.base, "--offline"], "create"), + ).toStrictEqual({ + cancelled: false, + github: mockOptions.github, + options: { + ...emptyOptions, + ...mockOptions, + access: "public", + description: "mock", + email: { + github: "mock", + npm: "mock", + }, + logo: undefined, + mode: "create", + offline: true, + owner: "mock", + skipAllContributorsApi: true, + skipGitHubApi: true, + title: "mock", + }, + }); + }); +}); diff --git a/src/shared/options/readOptions.ts b/src/shared/options/readOptions.ts new file mode 100644 index 00000000..61940364 --- /dev/null +++ b/src/shared/options/readOptions.ts @@ -0,0 +1,221 @@ +import { parseArgs } from "node:util"; +import { titleCase } from "title-case"; +import { z } from "zod"; + +import { Mode } from "../../bin/mode.js"; +import { withSpinner } from "../cli/spinners.js"; +import { Options, OptionsLogo } from "../types.js"; +import { allArgOptions } from "./args.js"; +import { augmentOptionsWithExcludes } from "./augmentOptionsWithExcludes.js"; +import { detectEmailRedundancy } from "./detectEmailRedundancy.js"; +import { ensureRepositoryExists } from "./ensureRepositoryExists.js"; +import { GitHub, getGitHub } from "./getGitHub.js"; +import { getPrefillOrPromptedOption } from "./getPrefillOrPromptedOption.js"; +import { optionsSchema } from "./optionsSchema.js"; +import { readOptionDefaults } from "./readOptionDefaults/index.js"; + +export interface GitHubAndOptions { + github: GitHub | undefined; + options: Options; +} + +export interface OptionsParseCancelled { + cancelled: true; + error?: string | z.ZodError; + options: object; +} + +export interface OptionsParseSuccess extends GitHubAndOptions { + cancelled: false; +} + +export type OptionsParseResult = OptionsParseCancelled | OptionsParseSuccess; + +export async function readOptions( + args: string[], + mode: Mode, +): Promise { + const defaults = readOptionDefaults(); + const { values } = parseArgs({ + args, + options: allArgOptions, + strict: false, + tokens: true, + }); + + const mappedOptions = { + access: values.access, + author: values.author, + base: values.base, + createRepository: values["create-repository"], + description: values.description, + email: + values.email ?? values["email-github"] ?? values["email-npm"] + ? { + github: values.email ?? values["email-github"], + npm: values.email ?? values["email-npm"], + } + : undefined, + excludeAllContributors: values["exclude-all-contributors"], + excludeCompliance: values["exclude-compliance"], + excludeLintDeprecation: values["exclude-lint-deprecation"], + excludeLintESLint: values["exclude-lint-eslint"], + excludeLintJSDoc: values["exclude-lint-jsdoc"], + excludeLintJson: values["exclude-lint-json"], + excludeLintKnip: values["exclude-lint-knip"], + excludeLintMd: values["exclude-lint-md"], + excludeLintPackageJson: values["exclude-lint-package-json"], + excludeLintPackages: values["exclude-lint-packages"], + excludeLintPerfectionist: values["exclude-lint-perfectionist"], + excludeLintRegex: values["exclude-lint-regex"], + excludeLintSpelling: values["exclude-lint-spelling"], + excludeLintStrict: values["exclude-lint-strict"], + excludeLintYml: values["exclude-lint-yml"], + excludeReleases: values["exclude-releases"], + excludeRenovate: values["exclude-renovate"], + excludeTests: values["unit-tests"], + funding: values.funding, + offline: values.offline, + owner: values.owner, + repository: values.repository, + skipAllContributorsApi: + values["skip-all-contributors-api"] ?? values.offline, + skipGitHubApi: values["skip-github-api"] ?? values.offline, + skipInstall: values["skip-install"], + skipRemoval: values["skip-removal"], + skipRestore: values["skip-restore"], + skipUninstall: values["skip-uninstall"], + title: values.title, + }; + + const emailError = detectEmailRedundancy(values); + if (emailError) { + return { + cancelled: true, + error: emailError, + options: mappedOptions, + }; + } + + const optionsParseResult = optionsSchema.safeParse(mappedOptions); + + if (!optionsParseResult.success) { + return { + cancelled: true, + error: optionsParseResult.error, + options: mappedOptions, + }; + } + + const options = optionsParseResult.data; + + options.owner ??= await getPrefillOrPromptedOption( + options.owner, + "What organization or user will the repository be under?", + defaults.owner, + ); + if (!options.owner) { + return { + cancelled: true, + options, + }; + } + + options.repository ??= await getPrefillOrPromptedOption( + options.repository, + "What will the kebab-case name of the repository be?", + defaults.repository, + ); + if (!options.repository) { + return { + cancelled: true, + options, + }; + } + + const { github, repository } = await ensureRepositoryExists( + options.skipGitHubApi + ? undefined + : await withSpinner("Checking GitHub authentication", getGitHub), + { + createRepository: options.createRepository, + owner: options.owner, + repository: options.repository, + }, + ); + if (!repository) { + return { cancelled: true, options }; + } + + options.description ??= await getPrefillOrPromptedOption( + options.description, + "How would you describe the new package?", + async () => + (await defaults.description()) ?? "A very lovely package. Hooray!", + ); + if (!options.description) { + return { cancelled: true, options }; + } + + options.title ??= await getPrefillOrPromptedOption( + options.title, + "What will the Title Case title of the repository be?", + async () => + (await defaults.title()) ?? titleCase(repository).replaceAll("-", " "), + ); + if (!options.title) { + return { cancelled: true, options }; + } + + let logo: OptionsLogo | undefined; + + if (options.logo) { + const alt = await getPrefillOrPromptedOption( + options.logoAlt, + "What is the alt text (non-visual description) of the logo?", + ); + if (!alt) { + return { cancelled: true, options }; + } + + logo = { alt, src: options.logo }; + } + + const email = + options.email ?? + (await defaults.email()) ?? + (await getPrefillOrPromptedOption( + undefined, + "What email should be used in package.json and .md files?", + )); + if (!email) { + return { cancelled: true, options }; + } + + const augmentedOptions = await augmentOptionsWithExcludes({ + ...options, + access: options.access ?? "public", + author: options.author ?? (await defaults.owner()), + description: options.description, + email: typeof email === "string" ? { github: email, npm: email } : email, + funding: options.funding ?? (await defaults.funding()), + logo, + mode, + owner: options.owner, + repository, + title: options.title, + }); + + if (!augmentedOptions) { + return { + cancelled: true, + options, + }; + } + + return { + cancelled: false, + github, + options: augmentedOptions, + }; +} diff --git a/src/shared/packages.test.ts b/src/shared/packages.test.ts new file mode 100644 index 00000000..c8d61441 --- /dev/null +++ b/src/shared/packages.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it, vi } from "vitest"; + +import { removeDependencies } from "./packages.js"; + +const mockExecaCommand = vi.fn(); + +vi.mock("execa", () => ({ + get execaCommand() { + return mockExecaCommand; + }, +})); + +describe("removeDependencies", () => { + it("removes all packages that already exist when all already exist", async () => { + await removeDependencies(["one", "two"], { + one: "1.2.3", + two: "4.5.6", + }); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot(` + [ + [ + "pnpm remove one two", + ], + ] + `); + }); + + it("removes only packages that already exist when some don't exist", async () => { + await removeDependencies(["exists", "missing"], { + exists: "1.2.3", + }); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot(` + [ + [ + "pnpm remove exists", + ], + ] + `); + }); + + it("adds a flag to removing packages when one is provided", async () => { + await removeDependencies( + ["exists", "missing"], + { + exists: "1.2.3", + }, + "-D", + ); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot(` + [ + [ + "pnpm remove exists -D", + ], + ] + `); + }); + + it("does nothing when no packages already exist", async () => { + await removeDependencies(["missing"]); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot("[]"); + }); +}); diff --git a/src/shared/packages.ts b/src/shared/packages.ts new file mode 100644 index 00000000..ae678392 --- /dev/null +++ b/src/shared/packages.ts @@ -0,0 +1,24 @@ +import { execaCommand } from "execa"; + +import { readFileSafe } from "./readFileSafe.js"; +import { PartialPackageData } from "./types.js"; + +export async function readPackageData() { + return JSON.parse( + await readFileSafe("./package.json", "{}"), + ) as PartialPackageData; +} + +export async function removeDependencies( + packageNames: string[], + existing: Record = {}, + flags = "", +) { + const present = packageNames.filter((packageName) => packageName in existing); + + if (present.length) { + await execaCommand( + `pnpm remove ${present.join(" ")}${flags ? ` ${flags}` : ""}`, + ); + } +} diff --git a/src/shared/prompts.ts b/src/shared/prompts.ts new file mode 100644 index 00000000..a8863453 --- /dev/null +++ b/src/shared/prompts.ts @@ -0,0 +1,5 @@ +import * as prompts from "@clack/prompts"; + +export function filterPromptCancel(value: Value | symbol) { + return prompts.isCancel(value) ? undefined : value; +} diff --git a/src/shared/readFileAsJson.test.ts b/src/shared/readFileAsJson.test.ts new file mode 100644 index 00000000..90105897 --- /dev/null +++ b/src/shared/readFileAsJson.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it, vi } from "vitest"; + +import { readFileAsJson } from "./readFileAsJson.js"; + +const mockReadFile = vi.fn(); + +vi.mock("node:fs/promises", () => ({ + get readFile() { + return mockReadFile; + }, +})); + +describe("readFileAsJson", () => { + it("returns the file's parsed contents when it exists", async () => { + const data = { abc: 123 }; + + mockReadFile.mockResolvedValue(JSON.stringify(data)); + + const actual = await readFileAsJson("filePath.json"); + + expect(actual).toEqual(data); + }); + + it("throws an error when the file doesn't exist", async () => { + const error = new Error("Oh no!"); + + mockReadFile.mockRejectedValue(error); + + await expect(() => readFileAsJson("filePath.json")).rejects.toEqual( + new Error( + `Could not read file from filePath.json as JSON. Please ensure the file exists and is valid JSON.`, + { cause: error }, + ), + ); + }); +}); diff --git a/src/shared/readFileAsJson.ts b/src/shared/readFileAsJson.ts new file mode 100644 index 00000000..ea501dfd --- /dev/null +++ b/src/shared/readFileAsJson.ts @@ -0,0 +1,12 @@ +import * as fs from "node:fs/promises"; + +export async function readFileAsJson(filePath: string) { + try { + return JSON.parse((await fs.readFile(filePath)).toString()) as unknown; + } catch (error) { + throw new Error( + `Could not read file from ${filePath} as JSON. Please ensure the file exists and is valid JSON.`, + { cause: error }, + ); + } +} diff --git a/src/shared/readFileSafe.test.ts b/src/shared/readFileSafe.test.ts new file mode 100644 index 00000000..c910ca46 --- /dev/null +++ b/src/shared/readFileSafe.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it, vi } from "vitest"; + +import { readFileSafe } from "./readFileSafe.js"; + +const mockReadFile = vi.fn(); + +vi.mock("node:fs/promises", () => ({ + get default() { + return { + get readFile() { + return mockReadFile; + }, + }; + }, +})); + +describe("readFundingIfExists", () => { + it("outputs the file content as string when it exists", async () => { + mockReadFile.mockResolvedValue("File content as string"); + const result = await readFileSafe("/path/to/file.ext", "fallback"); + expect(result).toBe("File content as string"); + }); + + it("returns fallback when readFile fails", async () => { + mockReadFile.mockRejectedValue("Oops"); + const result = await readFileSafe("/path/to/nowhere.ext", "fallback"); + expect(result).toBe("fallback"); + }); +}); diff --git a/src/shared/readFileSafe.ts b/src/shared/readFileSafe.ts new file mode 100644 index 00000000..32815547 --- /dev/null +++ b/src/shared/readFileSafe.ts @@ -0,0 +1,9 @@ +import fs from "node:fs/promises"; + +export async function readFileSafe(filePath: string, fallback: string) { + try { + return (await fs.readFile(filePath)).toString(); + } catch { + return fallback; + } +} diff --git a/src/shared/readFileSafeAsJson.ts b/src/shared/readFileSafeAsJson.ts new file mode 100644 index 00000000..27b43c0f --- /dev/null +++ b/src/shared/readFileSafeAsJson.ts @@ -0,0 +1,5 @@ +import { readFileSafe } from "./readFileSafe.js"; + +export async function readFileSafeAsJson(filePath: string) { + return JSON.parse(await readFileSafe(filePath, "null")) as unknown; +} diff --git a/src/shared/runOrRestore.test.ts b/src/shared/runOrRestore.test.ts new file mode 100644 index 00000000..b7f9edc9 --- /dev/null +++ b/src/shared/runOrRestore.test.ts @@ -0,0 +1,95 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { runOrRestore } from "./runOrRestore.js"; + +const mockConfirm = vi.fn(); + +vi.mock("@clack/prompts", () => ({ + get confirm() { + return mockConfirm; + }, + intro: vi.fn(), + isCancel: vi.fn(), + outro: vi.fn(), +})); + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const mockGetInputValuesAndOctokit = vi.fn(); + +vi.mock("./inputs.js", () => ({ + get getInputValuesAndOctokit() { + return mockGetInputValuesAndOctokit; + }, +})); + +describe("runOrRestore", () => { + beforeEach(() => { + vi.spyOn(console, "clear").mockImplementation(() => undefined); + vi.spyOn(console, "log").mockImplementation(() => undefined); + }); + + it("returns 0 when run resolves", async () => { + mockGetInputValuesAndOctokit.mockResolvedValue({ + octokit: undefined, + values: {}, + }); + + const actual = await runOrRestore({ + run: vi.fn(), + skipRestore: true, + }); + + expect(actual).toBe(0); + }); + + it("returns 2 and does not restore the repository when run rejects and skipRestore is true", async () => { + mockGetInputValuesAndOctokit.mockResolvedValue({ + octokit: undefined, + values: { + skipRestore: false, + }, + }); + mockConfirm.mockResolvedValue(false); + + const actual = await runOrRestore({ + run: vi.fn().mockRejectedValue(new Error("Oh no!")), + skipRestore: true, + }); + + expect(actual).toBe(2); + expect(mock$).toHaveBeenCalledTimes(0); + }); + + it("returns 2 and restores the repository when run rejects and skipRestore is false", async () => { + mockGetInputValuesAndOctokit.mockResolvedValue({ + octokit: undefined, + values: { + skipRestore: false, + }, + }); + mockConfirm.mockResolvedValue(true); + + const actual = await runOrRestore({ + run: vi.fn().mockRejectedValue(new Error("Oh no!")), + skipRestore: false, + }); + + expect(actual).toBe(2); + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "git restore .", + ], + ], + ] + `); + }); +}); diff --git a/src/shared/runOrRestore.ts b/src/shared/runOrRestore.ts new file mode 100644 index 00000000..3fba6ff3 --- /dev/null +++ b/src/shared/runOrRestore.ts @@ -0,0 +1,42 @@ +import * as prompts from "@clack/prompts"; +import chalk from "chalk"; +import { $ } from "execa"; + +import { logLine } from "./cli/lines.js"; + +export interface RunOrRestoreOptions { + run: () => Promise; + skipRestore: boolean | undefined; +} + +export async function runOrRestore({ run, skipRestore }: RunOrRestoreOptions) { + try { + await run(); + return 0; + } catch (error) { + logLine(); + console.log(error); + + if (skipRestore) { + logLine(); + logLine(chalk.gray`Leaving changes to the local directory on disk.`); + } else { + const shouldRestore = await prompts.confirm({ + message: "Do you want to restore the repository to how it was?", + }); + + if (shouldRestore) { + logLine(); + logLine( + [ + chalk.gray`Resetting repository using`, + chalk.reset`git restore .`, + ].join(" "), + ); + await $`git restore .`; + } + } + + return 2; + } +} diff --git a/src/shared/tryCatchAsync.ts b/src/shared/tryCatchAsync.ts new file mode 100644 index 00000000..7765510a --- /dev/null +++ b/src/shared/tryCatchAsync.ts @@ -0,0 +1,7 @@ +export async function tryCatchAsync(get: () => Promise) { + try { + return await get(); + } catch { + return undefined; + } +} diff --git a/src/shared/tryCatchLazyValueAsync.test.ts b/src/shared/tryCatchLazyValueAsync.test.ts new file mode 100644 index 00000000..e1fca149 --- /dev/null +++ b/src/shared/tryCatchLazyValueAsync.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it, vi } from "vitest"; + +import { tryCatchLazyValueAsync } from "./tryCatchLazyValueAsync.js"; + +describe("tryCatchLazyValueAsync", () => { + it("does not run get when it has not been called", () => { + const get = vi.fn(); + + tryCatchLazyValueAsync(get); + + expect(get).not.toHaveBeenCalled(); + }); + + it("returns get's resolved value when it resolves", async () => { + const expected = "value"; + const get = vi.fn().mockResolvedValue(expected); + + const lazy = tryCatchLazyValueAsync(get); + + expect(await lazy()).toEqual(expected); + }); + + it("returns undefined when get rejects", async () => { + const get = vi.fn().mockRejectedValue(new Error("Oh no!")); + + const lazy = tryCatchLazyValueAsync(get); + + expect(await lazy()).toBeUndefined(); + }); +}); diff --git a/src/shared/tryCatchLazyValueAsync.ts b/src/shared/tryCatchLazyValueAsync.ts new file mode 100644 index 00000000..b8215d15 --- /dev/null +++ b/src/shared/tryCatchLazyValueAsync.ts @@ -0,0 +1,7 @@ +import lazyValue from "lazy-value"; + +import { tryCatchAsync } from "./tryCatchAsync.js"; + +export function tryCatchLazyValueAsync(get: () => Promise) { + return lazyValue(async () => await tryCatchAsync(get)); +} diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 00000000..0d515845 --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,76 @@ +import { Mode } from "../bin/mode.js"; + +export interface AllContributorContributor { + contributions: string[]; + login: string; +} + +export interface AllContributorsData { + contributors: AllContributorContributor[]; +} + +export interface PartialPackageData { + author?: { email: string; name: string } | string; + dependencies?: Record; + description?: string; + devDependencies?: Record; + email?: string; + name?: string; + repository?: { type: string; url: string } | string; +} + +export type OptionsAccess = "public" | "restricted"; + +export type OptionsBase = "common" | "everything" | "minimum" | "prompt"; + +export interface OptionsEmail { + github: string; + npm: string; +} + +export interface OptionsLogo { + alt: string; + src: string; +} + +export interface Options { + access: OptionsAccess; + author?: string; + base?: OptionsBase; + createRepository?: boolean; + description: string; + email: OptionsEmail; + excludeAllContributors?: boolean; + excludeCompliance?: boolean; + excludeLintDeprecation?: boolean; + excludeLintESLint?: boolean; + excludeLintJSDoc?: boolean; + excludeLintJson?: boolean; + excludeLintKnip?: boolean; + excludeLintMd?: boolean; + excludeLintPackageJson?: boolean; + excludeLintPackages?: boolean; + excludeLintPerfectionist?: boolean; + excludeLintRegex?: boolean; + excludeLintSpelling?: boolean; + excludeLintStrict?: boolean; + excludeLintStylistic?: boolean; + excludeLintYml?: boolean; + excludeReleases?: boolean; + excludeRenovate?: boolean; + excludeTests?: boolean; + funding?: string; + keywords?: string[]; + logo: OptionsLogo | undefined; + mode: Mode; + offline?: boolean; + owner: string; + repository: string; + skipAllContributorsApi?: boolean; + skipGitHubApi?: boolean; + skipInstall?: boolean; + skipRemoval?: boolean; + skipRestore?: boolean; + skipUninstall?: boolean; + title: string; +} diff --git a/src/steps/addOwnerAsAllContributor.test.ts b/src/steps/addOwnerAsAllContributor.test.ts new file mode 100644 index 00000000..18f419bc --- /dev/null +++ b/src/steps/addOwnerAsAllContributor.test.ts @@ -0,0 +1,114 @@ +import prettier from "prettier"; +import { describe, expect, it, vi } from "vitest"; + +import { addOwnerAsAllContributor } from "./addOwnerAsAllContributor.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const mockWriteFile = vi.fn(); + +vi.mock("node:fs/promises", () => ({ + get default() { + return { + get writeFile() { + return mockWriteFile; + }, + }; + }, +})); + +const mockReadFileAsJson = vi.fn(); + +vi.mock("../shared/readFileAsJson.js", () => ({ + get readFileAsJson() { + return mockReadFileAsJson; + }, +})); + +const mockOwner = "TestOwner"; + +vi.mock("../shared/getGitHubUserAsAllContributor", () => ({ + getGitHubUserAsAllContributor: () => mockOwner, +})); + +describe("addOwnerAsAllContributor", () => { + it("throws an error when the .all-contributorsrc fails to read", async () => { + mock$.mockResolvedValueOnce({ + stdout: JSON.stringify({ login: "user" }), + }); + mockReadFileAsJson.mockResolvedValue("invalid"); + + await expect(async () => { + await addOwnerAsAllContributor({ owner: mockOwner }); + }).rejects.toMatchInlineSnapshot( + '[Error: Invalid .all-contributorsrc: "invalid"]', + ); + }); + + it("throws an error when the .all-contributorsrc is missing expected properties", async () => { + mock$.mockResolvedValueOnce({ + stdout: JSON.stringify({ login: "user" }), + }); + mockReadFileAsJson.mockResolvedValue({}); + + await expect(async () => { + await addOwnerAsAllContributor({ owner: mockOwner }); + }).rejects.toMatchInlineSnapshot( + "[Error: Invalid .all-contributorsrc: {}]", + ); + }); + + it("sets the running user to tool when no prior contributions exist", async () => { + mock$.mockResolvedValueOnce({ + stdout: JSON.stringify({ login: mockOwner }), + }); + mockReadFileAsJson.mockResolvedValue({ + contributors: [], + }); + + await addOwnerAsAllContributor({ owner: mockOwner }); + + expect(mockWriteFile).toHaveBeenCalledWith( + "./.all-contributorsrc", + await prettier.format( + JSON.stringify({ + contributors: [{ contributions: ["tool"], login: mockOwner }], + }), + { parser: "json" }, + ), + ); + }); + + it("resets JoshuaKGoldberg to just tool and adds in the running user when both exist", async () => { + mock$.mockResolvedValueOnce({ + stdout: JSON.stringify({ login: mockOwner }), + }); + mockReadFileAsJson.mockResolvedValue({ + contributors: [ + { contributions: ["bug", "fix"], login: mockOwner }, + { contributions: ["bug", "fix"], login: "JoshuaKGoldberg" }, + ], + }); + + await addOwnerAsAllContributor({ owner: mockOwner }); + + expect(mockWriteFile).toHaveBeenCalledWith( + "./.all-contributorsrc", + await prettier.format( + JSON.stringify({ + contributors: [ + { contributions: ["bug", "fix", "tool"], login: mockOwner }, + { contributions: ["tool"], login: "JoshuaKGoldberg" }, + ], + }), + { parser: "json" }, + ), + ); + }); +}); diff --git a/src/steps/addOwnerAsAllContributor.ts b/src/steps/addOwnerAsAllContributor.ts new file mode 100644 index 00000000..6a3b6862 --- /dev/null +++ b/src/steps/addOwnerAsAllContributor.ts @@ -0,0 +1,58 @@ +import fs from "node:fs/promises"; +import prettier from "prettier"; + +import { getGitHubUserAsAllContributor } from "../shared/getGitHubUserAsAllContributor.js"; +import { readFileAsJson } from "../shared/readFileAsJson.js"; +import { AllContributorsData, Options } from "../shared/types.js"; + +export async function addOwnerAsAllContributor( + options: Pick, +) { + const user = await getGitHubUserAsAllContributor(options); + + const existingContributors = (await readFileAsJson( + "./.all-contributorsrc", + )) as AllContributorsData; + if (!isValidAllContributorsData(existingContributors)) { + throw new Error( + `Invalid .all-contributorsrc: ${JSON.stringify(existingContributors)}`, + ); + } + + const contributors = existingContributors.contributors + .filter(({ login }) => ["JoshuaKGoldberg", user].includes(login)) + .map((contributor) => + contributor.login === "JoshuaKGoldberg" + ? { ...contributor, contributions: ["tool"] } + : { + ...contributor, + contributions: Array.from( + new Set([...contributor.contributions, "tool"]), + ), + }, + ); + + if (!contributors.some((contributor) => contributor.login === user)) { + contributors.push({ + contributions: ["tool"], + login: user, + }); + } + + await fs.writeFile( + "./.all-contributorsrc", + await prettier.format( + JSON.stringify({ + ...existingContributors, + contributors, + }), + { parser: "json" }, + ), + ); +} + +function isValidAllContributorsData( + value: unknown, +): value is AllContributorsData { + return !!value && typeof value === "object" && "contributors" in value; +} diff --git a/src/steps/addToolAllContributors.test.ts b/src/steps/addToolAllContributors.test.ts new file mode 100644 index 00000000..5ddedcb7 --- /dev/null +++ b/src/steps/addToolAllContributors.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, vi } from "vitest"; + +import { addToolAllContributors } from "./addToolAllContributors.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const mockGetGitHubUserAsAllContributor = vi.fn(); + +vi.mock("../shared/getGitHubUserAsAllContributor.js", () => ({ + get getGitHubUserAsAllContributor() { + return mockGetGitHubUserAsAllContributor; + }, +})); + +describe("addToolAllContributors", () => { + it("adds JoshuaKGoldberg when that is not the current github user", async () => { + mockGetGitHubUserAsAllContributor.mockResolvedValue("JoshuaKGoldberg"); + + await addToolAllContributors({ owner: "owner" }); + + expect(mock$).not.toHaveBeenCalled(); + }); + + it("does not add JoshuaKGoldberg when that not the current github user", async () => { + mockGetGitHubUserAsAllContributor.mockResolvedValue("other"); + + await addToolAllContributors({ owner: "owner" }); + + expect(mock$).toHaveBeenCalledWith([ + `npx -y all-contributors-cli add JoshuaKGoldberg tool`, + ]); + }); +}); diff --git a/src/steps/addToolAllContributors.ts b/src/steps/addToolAllContributors.ts new file mode 100644 index 00000000..907d15d8 --- /dev/null +++ b/src/steps/addToolAllContributors.ts @@ -0,0 +1,14 @@ +import { $ } from "execa"; + +import { getGitHubUserAsAllContributor } from "../shared/getGitHubUserAsAllContributor.js"; +import { Options } from "../shared/types.js"; + +export async function addToolAllContributors( + options: Pick, +) { + const login = await getGitHubUserAsAllContributor(options); + + if (login !== "JoshuaKGoldberg") { + await $`npx -y all-contributors-cli add JoshuaKGoldberg tool`; + } +} diff --git a/src/steps/clearChangelog.ts b/src/steps/clearChangelog.ts new file mode 100644 index 00000000..d543f6d0 --- /dev/null +++ b/src/steps/clearChangelog.ts @@ -0,0 +1,9 @@ +import fs from "node:fs/promises"; +import prettier from "prettier"; + +export async function clearChangelog() { + await fs.writeFile( + "./CHANGELOG.md", + await prettier.format(`# Changelog`, { parser: "markdown" }), + ); +} diff --git a/src/steps/clearUnnecessaryFiles.ts b/src/steps/clearUnnecessaryFiles.ts new file mode 100644 index 00000000..e67d1cb3 --- /dev/null +++ b/src/steps/clearUnnecessaryFiles.ts @@ -0,0 +1,32 @@ +import fs from "node:fs/promises"; + +const globPaths = [ + ...extensions(".babelrc", "cjs", "cts", "js", "json", "mjs"), + ...extensions(".eslintrc", "js", "json", "yml"), + ...extensions(".prettierrc", "json", "json5", "yaml", "yml"), + ...extensions("babel.config", "cjs", "cts", "js", "json", "mjs"), + ...extensions("jest.config", "cjs", "js", "json", "mjs", "ts"), + "./src/**/*.js", + ".circleci/config.yml", + ".babelrc", + ".npmignore", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "codecov.yml", + "DEVELOPMENT.md", + "dist", + "lib", + "package-lock.json", + "travis.yml", + "yarn.lock", +]; + +function extensions(base: string, ...extensions: string[]) { + return extensions.map((extension) => [base, extension].join(".")); +} + +export async function clearUnnecessaryFiles() { + for (const globPath of globPaths) { + await fs.rm(globPath, { force: true, recursive: true }); + } +} diff --git a/src/steps/detectExistingContributors.test.ts b/src/steps/detectExistingContributors.test.ts new file mode 100644 index 00000000..c5aae0a0 --- /dev/null +++ b/src/steps/detectExistingContributors.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from "vitest"; + +import { detectExistingContributors } from "./detectExistingContributors.js"; + +const mockGetAllContributorsForRepository = vi.fn(); + +vi.mock("all-contributors-for-repository", () => ({ + get getAllContributorsForRepository() { + return mockGetAllContributorsForRepository; + }, +})); + +const mock$ = vi.fn().mockImplementation(() => mock$); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const options = { + owner: "TestOwner", + repository: "test-repository", +}; + +describe("detectExistingContributors", () => { + it("runs npx all-contributors add for each contributor and contribution type", async () => { + mockGetAllContributorsForRepository.mockResolvedValue({ + username: ["bug", "docs"], + }); + + await detectExistingContributors("auth-token", options); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "env": { + "PRIVATE_TOKEN": "auth-token", + }, + }, + ], + [ + [ + "npx -y all-contributors-cli add ", + " ", + "", + ], + "username", + "0,1", + ], + ] + `); + }); +}); diff --git a/src/steps/detectExistingContributors.ts b/src/steps/detectExistingContributors.ts new file mode 100644 index 00000000..c1134821 --- /dev/null +++ b/src/steps/detectExistingContributors.ts @@ -0,0 +1,22 @@ +import { getAllContributorsForRepository } from "all-contributors-for-repository"; +import { $ } from "execa"; + +import { Options } from "../shared/types.js"; + +export async function detectExistingContributors( + auth: string | undefined, + options: Pick, +) { + const contributors = await getAllContributorsForRepository({ + auth, + owner: options.owner, + repo: options.repository, + }); + + for (const [contributor, contributions] of Object.entries(contributors)) { + const contributionTypes = Object.keys(contributions).join(","); + await $({ + env: { PRIVATE_TOKEN: auth }, + })`npx -y all-contributors-cli add ${contributor} ${contributionTypes}`; + } +} diff --git a/src/steps/finalizeDependencies.test.ts b/src/steps/finalizeDependencies.test.ts new file mode 100644 index 00000000..4c86ea4c --- /dev/null +++ b/src/steps/finalizeDependencies.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, it, vi } from "vitest"; + +import { Options } from "../shared/types.js"; +import { finalizeDependencies } from "./finalizeDependencies.js"; + +const mockExecaCommand = vi.fn(); + +vi.mock("execa", () => ({ + get execaCommand() { + return mockExecaCommand; + }, +})); + +vi.mock("../shared/packages.js", () => ({ + readPackageData: () => [], + removeDependencies: vi.fn(), +})); + +const options = { + access: "public", + author: undefined, + base: "everything", + createRepository: undefined, + description: "Stub description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintSpelling: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + logo: undefined, + mode: "create", + offline: false, + owner: "StubOwner", + repository: "stub-repository", + skipGitHubApi: false, + skipInstall: undefined, + skipRemoval: undefined, + skipRestore: undefined, + skipUninstall: undefined, + title: "Stub Title", +} satisfies Options; + +describe("finalize", () => { + it("installs the full list of commands when no options are enabled", async () => { + await finalizeDependencies(options); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot(` + [ + [ + "pnpm add @release-it/conventional-changelog@latest @types/eslint@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest @vitest/coverage-v8@latest all-contributors-cli@latest console-fail-test@latest cspell@latest eslint@latest eslint-plugin-deprecation@latest eslint-plugin-eslint-comments@latest eslint-plugin-jsdoc@latest eslint-plugin-jsonc@latest eslint-plugin-markdown@latest eslint-plugin-n@latest eslint-plugin-no-only-tests@latest eslint-plugin-perfectionist@latest eslint-plugin-regexp@latest eslint-plugin-vitest@latest eslint-plugin-yml@latest husky@latest jsonc-eslint-parser@latest knip@latest lint-staged@latest markdownlint@latest markdownlint-cli@latest npm-package-json-lint@latest npm-package-json-lint-config-default@latest prettier@latest prettier-plugin-curly@latest prettier-plugin-packagejson@latest release-it@latest sentences-per-line@latest should-semantic-release@latest tsup@latest typescript@latest vitest@latest yaml-eslint-parser@latest -D", + ], + [ + "npx all-contributors-cli generate", + ], + [ + "pnpm dedupe", + ], + ] + `); + }); + + it("installs in offline mode when options.offline is true", async () => { + await finalizeDependencies({ + ...options, + offline: true, + }); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot(` + [ + [ + "pnpm add @release-it/conventional-changelog@latest @types/eslint@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest @vitest/coverage-v8@latest all-contributors-cli@latest console-fail-test@latest cspell@latest eslint@latest eslint-plugin-deprecation@latest eslint-plugin-eslint-comments@latest eslint-plugin-jsdoc@latest eslint-plugin-jsonc@latest eslint-plugin-markdown@latest eslint-plugin-n@latest eslint-plugin-no-only-tests@latest eslint-plugin-perfectionist@latest eslint-plugin-regexp@latest eslint-plugin-vitest@latest eslint-plugin-yml@latest husky@latest jsonc-eslint-parser@latest knip@latest lint-staged@latest markdownlint@latest markdownlint-cli@latest npm-package-json-lint@latest npm-package-json-lint-config-default@latest prettier@latest prettier-plugin-curly@latest prettier-plugin-packagejson@latest release-it@latest sentences-per-line@latest should-semantic-release@latest tsup@latest typescript@latest vitest@latest yaml-eslint-parser@latest -D --offline", + ], + [ + "npx all-contributors-cli generate", + ], + [ + "pnpm dedupe", + ], + ] + `); + }); + + it("installs the base list of commands when all options are enabled", async () => { + await finalizeDependencies({ + ...options, + excludeAllContributors: true, + excludeCompliance: true, + excludeLintJson: true, + excludeLintKnip: true, + excludeLintMd: true, + excludeLintPackageJson: true, + excludeLintPackages: true, + excludeLintPerfectionist: true, + excludeLintSpelling: true, + excludeLintYml: true, + excludeReleases: true, + excludeRenovate: undefined, + excludeTests: true, + }); + + expect(mockExecaCommand.mock.calls).toMatchInlineSnapshot(` + [ + [ + "pnpm add @types/eslint@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest eslint@latest eslint-plugin-deprecation@latest eslint-plugin-eslint-comments@latest eslint-plugin-jsdoc@latest eslint-plugin-n@latest eslint-plugin-regexp@latest husky@latest lint-staged@latest prettier@latest prettier-plugin-curly@latest prettier-plugin-packagejson@latest tsup@latest typescript@latest -D", + ], + [ + "pnpm dedupe", + ], + ] + `); + }); +}); diff --git a/src/steps/finalizeDependencies.ts b/src/steps/finalizeDependencies.ts new file mode 100644 index 00000000..52e29430 --- /dev/null +++ b/src/steps/finalizeDependencies.ts @@ -0,0 +1,83 @@ +import { execaCommand } from "execa"; + +import { readPackageData, removeDependencies } from "../shared/packages.js"; +import { Options } from "../shared/types.js"; + +export async function finalizeDependencies(options: Options) { + const devDependencies = [ + "@types/eslint", + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + "eslint", + "eslint-plugin-deprecation", + "eslint-plugin-eslint-comments", + "eslint-plugin-jsdoc", + "eslint-plugin-n", + "eslint-plugin-regexp", + "husky", + "lint-staged", + "prettier", + "prettier-plugin-curly", + "prettier-plugin-packagejson", + "tsup", + "typescript", + ...(options.excludeAllContributors ? [] : ["all-contributors-cli"]), + ...(options.excludeLintJson + ? [] + : ["eslint-plugin-jsonc", "jsonc-eslint-parser"]), + ...(options.excludeLintKnip ? [] : ["knip"]), + ...(options.excludeLintMd + ? [] + : [ + "eslint-plugin-markdown", + "markdownlint", + "markdownlint-cli", + "sentences-per-line", + ]), + ...(options.excludeLintPackageJson + ? [] + : ["npm-package-json-lint", "npm-package-json-lint-config-default"]), + ...(options.excludeLintPerfectionist + ? [] + : ["eslint-plugin-perfectionist"]), + ...(options.excludeLintSpelling ? [] : ["cspell"]), + ...(options.excludeLintYml + ? [] + : ["eslint-plugin-yml", "yaml-eslint-parser"]), + ...(options.excludeReleases + ? [] + : [ + "@release-it/conventional-changelog", + "release-it", + "should-semantic-release", + ]), + ...(options.excludeTests + ? [] + : [ + "@vitest/coverage-v8", + "console-fail-test", + "eslint-plugin-no-only-tests", + "eslint-plugin-vitest", + "vitest", + ]), + ] + .filter(Boolean) + .sort() + .map((packageName) => `${packageName}@latest`) + .join(" "); + + await execaCommand( + `pnpm add ${devDependencies} -D${options.offline ? " --offline" : ""}`, + ); + + if (!options.excludeAllContributors) { + await execaCommand(`npx all-contributors-cli generate`); + await removeDependencies( + ["all-contributors-cli", "all-contributors-for-repository"], + (await readPackageData()).devDependencies, + "-D", + ); + } + + await execaCommand(`pnpm dedupe`); +} diff --git a/src/steps/initializeBranchProtectionSettings.test.ts b/src/steps/initializeBranchProtectionSettings.test.ts new file mode 100644 index 00000000..d75220a8 --- /dev/null +++ b/src/steps/initializeBranchProtectionSettings.test.ts @@ -0,0 +1,47 @@ +import { Octokit } from "octokit"; +import { SpyInstance, describe, expect, it, vi } from "vitest"; + +import { initializeBranchProtectionSettings } from "./initializeGitHubRepository/initializeBranchProtectionSettings.js"; + +const createMockOctokit = (request: SpyInstance) => + ({ + request, + }) as unknown as Octokit; + +const stubValues = { owner: "", repository: "" }; + +describe("migrateBranchProtectionSettings", () => { + it("does not throw when the request receives a non-error response", async () => { + const mockRequest = vi.fn().mockResolvedValue({ status: 200 }); + + await expect( + initializeBranchProtectionSettings( + createMockOctokit(mockRequest), + stubValues, + ), + ).resolves.not.toThrow(); + }); + + it("returns false when the request receives a 403 response", async () => { + const mockRequest = vi.fn().mockRejectedValue({ status: 403 }); + + const actual = await initializeBranchProtectionSettings( + createMockOctokit(mockRequest), + stubValues, + ); + + expect(actual).toBe(false); + }); + + it("throws the error when the request throws with a non-403 response", async () => { + const error = { status: 404 }; + const mockRequest = vi.fn().mockRejectedValue(error); + + await expect(() => + initializeBranchProtectionSettings( + createMockOctokit(mockRequest), + stubValues, + ), + ).rejects.toBe(error); + }); +}); diff --git a/src/steps/initializeGitHubRepository/index.ts b/src/steps/initializeGitHubRepository/index.ts new file mode 100644 index 00000000..5298e717 --- /dev/null +++ b/src/steps/initializeGitHubRepository/index.ts @@ -0,0 +1,17 @@ +import { Octokit } from "octokit"; + +import { Options } from "../../shared/types.js"; +import { initializeGitRemote } from "../initializeGitRemote.js"; +import { initializeRepositorySettings } from "../initializeRepositorySettings.js"; +import { initializeBranchProtectionSettings } from "./initializeBranchProtectionSettings.js"; +import { initializeRepositoryLabels } from "./labels/initializeRepositoryLabels.js"; + +export async function initializeGitHubRepository( + octokit: Octokit, + options: Options, +) { + await initializeGitRemote(options); + await initializeRepositorySettings(octokit, options); + await initializeBranchProtectionSettings(octokit, options); + await initializeRepositoryLabels(); +} diff --git a/src/steps/initializeGitHubRepository/initializeBranchProtectionSettings.ts b/src/steps/initializeGitHubRepository/initializeBranchProtectionSettings.ts new file mode 100644 index 00000000..9ae29e90 --- /dev/null +++ b/src/steps/initializeGitHubRepository/initializeBranchProtectionSettings.ts @@ -0,0 +1,51 @@ +import { RequestError } from "@octokit/request-error"; +import { Octokit } from "octokit"; + +import { Options } from "../../shared/types.js"; + +export async function initializeBranchProtectionSettings( + octokit: Octokit, + { owner, repository }: Pick, +) { + try { + await octokit.request( + `PUT /repos/${owner}/${repository}/branches/main/protection`, + { + allow_deletions: false, + allow_force_pushes: true, + allow_fork_pushes: false, + allow_fork_syncing: true, + block_creations: false, + branch: "main", + enforce_admins: false, + owner, + repo: repository, + required_conversation_resolution: true, + required_linear_history: false, + required_pull_request_reviews: null, + required_status_checks: { + checks: [ + { context: "build" }, + { context: "compliance" }, + { context: "lint" }, + { context: "lint_knip" }, + { context: "lint_markdown" }, + { context: "lint_package_json" }, + { context: "lint_packages" }, + { context: "lint_spelling" }, + { context: "prettier" }, + { context: "test" }, + ], + strict: false, + }, + restrictions: null, + }, + ); + } catch (error) { + if ((error as RequestError).status === 403) { + return false; + } + + throw error; + } +} diff --git a/src/steps/initializeGitHubRepository/labels/getExistingEquivalentLabels.test.ts b/src/steps/initializeGitHubRepository/labels/getExistingEquivalentLabels.test.ts new file mode 100644 index 00000000..66e747b9 --- /dev/null +++ b/src/steps/initializeGitHubRepository/labels/getExistingEquivalentLabels.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from "vitest"; + +import { getExistingEquivalentLabels } from "./getExistingEquivalentLabels.js"; + +const createLabel = (name: string) => ({ + color: "#000000", + description: "A good label.", + name, +}); + +describe("getExistingEquivalentLabels", () => { + it("returns no labels when there are no existing labels", () => { + const actual = getExistingEquivalentLabels([], "abc"); + + expect(actual).toEqual([]); + }); + + it("returns no labels when no existing label matches", () => { + const actual = getExistingEquivalentLabels([createLabel("abc")], "def"); + + expect(actual).toEqual([]); + }); + + it("returns an existing un-prefixed label when it matches by name", () => { + const abcLabel = createLabel("abc"); + const actual = getExistingEquivalentLabels( + [createLabel("def"), abcLabel, createLabel("ghi")], + "abc", + ); + + expect(actual).toEqual([abcLabel]); + }); + + it("returns an existing prefixed label when it matches by name", () => { + const abcDefLabel = createLabel("abc: def"); + const actual = getExistingEquivalentLabels([abcDefLabel], "abc: def"); + + expect(actual).toEqual([abcDefLabel]); + }); + + it("returns the existing label when it matches excluding prefix", () => { + const abcLabel = createLabel("abc"); + const actual = getExistingEquivalentLabels( + [createLabel("abc: def"), abcLabel, createLabel("ghi")], + "type: abc", + ); + + expect(actual).toEqual([abcLabel]); + }); + + it("returns the existing label when it matches an alias", () => { + const enhancementLabel = createLabel("enhancement"); + const actual = getExistingEquivalentLabels( + [createLabel("abc: def"), enhancementLabel, createLabel("ghi")], + "type: feature", + ); + + expect(actual).toEqual([enhancementLabel]); + }); + + it("returns both existing labels when one matches on name and another matches an alias", () => { + const enhancementLabel = createLabel("enhancement"); + const typeFeatureLabel = createLabel("type: feature"); + const actual = getExistingEquivalentLabels( + [ + createLabel("abc: def"), + enhancementLabel, + createLabel("ghi"), + typeFeatureLabel, + ], + "type: feature", + ); + + expect(actual).toEqual([enhancementLabel, typeFeatureLabel]); + }); +}); diff --git a/src/steps/initializeGitHubRepository/labels/getExistingEquivalentLabels.ts b/src/steps/initializeGitHubRepository/labels/getExistingEquivalentLabels.ts new file mode 100644 index 00000000..ab78a956 --- /dev/null +++ b/src/steps/initializeGitHubRepository/labels/getExistingEquivalentLabels.ts @@ -0,0 +1,26 @@ +const aliases = new Map([ + ["enhancement", "type: feature"], + ["help wanted", "status: accepting prs"], +]); + +export interface GhLabelData { + color: string; + description: string; + name: string; +} + +export function getExistingEquivalentLabels( + existingLabels: GhLabelData[], + outcomeLabelName: string, +) { + const outcomeTrimmed = outcomeLabelName.replace(/\w+: (\w+)/, "$1"); + + return existingLabels.filter(({ name: existingName }) => { + return ( + existingName === outcomeLabelName || + existingName === outcomeTrimmed || + aliases.get(existingName) === outcomeLabelName || + existingName.replace(/\w+: (\w+)/, "$1") === outcomeLabelName + ); + }); +} diff --git a/src/steps/initializeGitHubRepository/labels/initializeRepositoryLabels.test.ts b/src/steps/initializeGitHubRepository/labels/initializeRepositoryLabels.test.ts new file mode 100644 index 00000000..c750abd7 --- /dev/null +++ b/src/steps/initializeGitHubRepository/labels/initializeRepositoryLabels.test.ts @@ -0,0 +1,363 @@ +import { describe, expect, it, vi } from "vitest"; + +import { initializeRepositoryLabels } from "./initializeRepositoryLabels.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const mockOutcomeLabel = { + color: "000000", + description: "def ghi", + name: "area: abc", +}; + +vi.mock("./outcomeLabels.js", () => ({ + get outcomeLabels() { + return [mockOutcomeLabel]; + }, +})); + +describe("migrateRepositoryLabels", () => { + it("creates an outcome label when labels stdout is empty", async () => { + mock$.mockResolvedValue({ + stdout: "", + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label create ", + " --color ", + " --description ", + "", + ], + "area: abc", + "000000", + "def ghi", + ], + ] + `); + }); + + it("creates an outcome label when it doesn't already exist", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + color: "000000", + description: "def ghi", + name: "other", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label create ", + " --color ", + " --description ", + "", + ], + "area: abc", + "000000", + "def ghi", + ], + ] + `); + }); + + it("doesn't edit a outcome label when it already exists with the same information", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([mockOutcomeLabel]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + ] + `); + }); + + it("edits the outcome label when it already exists with different color", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + ...mockOutcomeLabel, + color: "111111", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label edit ", + " --color ", + " --description ", + " --name ", + "", + ], + "area: abc", + "000000", + "def ghi", + "area: abc", + ], + ] + `); + }); + + it("edits the outcome label when it already exists with a different description", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + ...mockOutcomeLabel, + description: "updated", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label edit ", + " --color ", + " --description ", + " --name ", + "", + ], + "area: abc", + "000000", + "def ghi", + "area: abc", + ], + ] + `); + }); + + it("deletes an existing non-outcome label when the equivalent outcome label already exists", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + color: "000000", + description: "def ghi", + name: "area: abc", + }, + { + color: "000000", + description: "def ghi", + name: "area: abc", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + ] + `); + }); + + it("deletes a pre-existing label when it isn't a outcome label", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + color: "000000", + description: "def ghi", + name: "unknown", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label create ", + " --color ", + " --description ", + "", + ], + "area: abc", + "000000", + "def ghi", + ], + ] + `); + }); + + it("deletes the existing duplicate outcome label and edits the label with the outcome name and different color when both exist", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + color: "000000", + description: "def ghi", + name: "abc", + }, + { + color: "111111", + description: "def ghi", + name: "area: abc", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label delete ", + " --yes", + ], + "abc", + ], + [ + [ + "gh label edit ", + " --color ", + " --description ", + " --name ", + "", + ], + "area: abc", + "000000", + "def ghi", + "area: abc", + ], + ] + `); + }); + + it("deletes the existing duplicate outcome label and does not edit the label with the outcome name and same information when both exist", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + color: "000000", + description: "def ghi", + name: "abc", + }, + { + color: "000000", + description: "def ghi", + name: "area: abc", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label delete ", + " --yes", + ], + "abc", + ], + ] + `); + }); + + it("doesn't delete a pre-existing label when it isn't a outcome label", async () => { + mock$.mockResolvedValue({ + stdout: JSON.stringify([ + { + color: "000000", + description: "def ghi", + name: "jkl", + }, + ]), + }); + + await initializeRepositoryLabels(); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "gh label list --json color,description,name", + ], + ], + [ + [ + "gh label create ", + " --color ", + " --description ", + "", + ], + "area: abc", + "000000", + "def ghi", + ], + ] + `); + }); +}); diff --git a/src/steps/initializeGitHubRepository/labels/initializeRepositoryLabels.ts b/src/steps/initializeGitHubRepository/labels/initializeRepositoryLabels.ts new file mode 100644 index 00000000..06cea952 --- /dev/null +++ b/src/steps/initializeGitHubRepository/labels/initializeRepositoryLabels.ts @@ -0,0 +1,49 @@ +import { $ } from "execa"; + +import { + GhLabelData, + getExistingEquivalentLabels, +} from "./getExistingEquivalentLabels.js"; +import { outcomeLabels } from "./outcomeLabels.js"; + +export async function initializeRepositoryLabels() { + const existingLabels = JSON.parse( + (await $`gh label list --json color,description,name`).stdout || "[]", + ) as GhLabelData[]; + + for (const outcome of outcomeLabels) { + const existingEquivalents = getExistingEquivalentLabels( + existingLabels, + outcome.name, + ); + + // Case: the repo has neither of the two label types + if (!existingEquivalents.length) { + await $`gh label create ${outcome.name} --color ${outcome.color} --description ${outcome.description}`; + continue; + } + + for (const existingEquivalent of existingEquivalents) { + // Case: the repo already has both prefixed and non-prefixed label name types + // E.g. both "area: documentation" and "documentation" + if ( + existingEquivalent.name !== outcome.name && + existingLabels.some((existing) => existing.name === outcome.name) + ) { + await $`gh label delete ${existingEquivalent.name} --yes`; + continue; + } + + // Case: the repo has one of the two label types, with >=1 different property + // E.g. "documentation" and the same color and description + // E.g. "area: documentation" but with a different color + if ( + outcome.color !== existingEquivalent.color || + outcome.description !== existingEquivalent.description || + outcome.name !== existingEquivalent.name + ) { + await $`gh label edit ${existingEquivalent.name} --color ${outcome.color} --description ${outcome.description} --name ${outcome.name}`; + } + } + } +} diff --git a/src/steps/initializeGitHubRepository/labels/outcomeLabels.ts b/src/steps/initializeGitHubRepository/labels/outcomeLabels.ts new file mode 100644 index 00000000..bea5d902 --- /dev/null +++ b/src/steps/initializeGitHubRepository/labels/outcomeLabels.ts @@ -0,0 +1,89 @@ +/* spellchecker: disable */ +export const outcomeLabels = [ + { + color: "0075ca", + description: "Improvements or additions to docs", + name: "area: documentation", + }, + { + color: "6009D7", + description: + "Improving how the repository's tests are run and/or code is tested", + name: "area: testing", + }, + { + color: "f9d0c4", + description: "Managing the repository's maintenance", + name: "area: tooling", + }, + { + color: "5319E7", + description: "Good for newcomers, please hop on!", + name: "good first issue", + }, + { + color: "7a5901", + description: "This doesn't seem right", + name: "invalid", + }, + { + color: "0E8A16", + description: "Please, send a pull request to resolve this!", + name: "status: accepting prs", + }, + { + color: "eeeeee", + description: "Issue is stale and/or no longer valid", + name: "status: aged away", + }, + { + color: "#ddcccc", + description: "Waiting for something else to be resolved", + name: "status: blocked", + }, + { + color: "cfd3d7", + description: "This issue or pull request already exists", + name: "status: duplicate", + }, + { + color: "#05104F", + description: "Not yet ready for implementation or a pull request", + name: "status: in discussion", + }, + { + color: "D3F82D", + description: "Further research required...?", + name: "status: needs investigation", + }, + { + color: "E4BC82", + description: "Needs an action taken by the original poster", + name: "status: waiting for author", + }, + { + color: "ffffff", + description: "This will not be worked on", + name: "status: wontfix", + }, + { + color: "d73a4a", + description: "Something isn't working :(", + name: "type: bug", + }, + { + color: "a2eeef", + description: "New enhancement or request", + name: "type: feature", + }, + { + color: "d876e3", + description: "Further information is requested", + name: "type: question", + }, + { + color: "fde282", + description: "Tech debt or other code/repository cleanups", + name: "type: cleanup", + }, +]; diff --git a/src/steps/initializeGitRemote.test.ts b/src/steps/initializeGitRemote.test.ts new file mode 100644 index 00000000..65b67d95 --- /dev/null +++ b/src/steps/initializeGitRemote.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it, vi } from "vitest"; + +import { initializeGitRemote } from "./initializeGitRemote.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +const options = { + owner: "TestOwner", + repository: "test-repository", +}; + +describe("initializeGitRemote", () => { + it("does not add an origin or fetch when remotes already includes origin", async () => { + mock$.mockResolvedValue({ + stdout: "origin", + }); + + await initializeGitRemote(options); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "git remote", + ], + ], + ] + `); + }); + + it("add an origin and fetch when remotes does not include origin", async () => { + mock$.mockResolvedValue({ + stdout: "", + }); + + await initializeGitRemote(options); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "git remote", + ], + ], + [ + [ + "git remote add origin https://github.com/", + "/", + "", + ], + "TestOwner", + "test-repository", + ], + [ + [ + "git fetch", + ], + ], + ] + `); + }); +}); diff --git a/src/steps/initializeGitRemote.ts b/src/steps/initializeGitRemote.ts new file mode 100644 index 00000000..46c7a70a --- /dev/null +++ b/src/steps/initializeGitRemote.ts @@ -0,0 +1,17 @@ +import { $ } from "execa"; + +import { Options } from "../shared/types.js"; + +type InitializeRepositorySettings = Pick; + +export async function initializeGitRemote({ + owner, + repository, +}: InitializeRepositorySettings) { + const remotes = (await $`git remote`).stdout.trim().split("\n"); + + if (!remotes.includes("origin")) { + await $`git remote add origin https://github.com/${owner}/${repository}`; + await $`git fetch`; + } +} diff --git a/src/steps/initializeRepositorySettings.ts b/src/steps/initializeRepositorySettings.ts new file mode 100644 index 00000000..063648eb --- /dev/null +++ b/src/steps/initializeRepositorySettings.ts @@ -0,0 +1,35 @@ +import { Octokit } from "octokit"; + +import { Options } from "../shared/types.js"; + +type InitializeRepositorySettings = Pick< + Options, + "description" | "owner" | "repository" +>; + +export async function initializeRepositorySettings( + octokit: Octokit, + { description, owner, repository }: InitializeRepositorySettings, +) { + await octokit.rest.repos.update({ + allow_auto_merge: true, + allow_rebase_merge: false, + allow_squash_merge: true, + default_branch: "main", + delete_branch_on_merge: true, + description, + has_wiki: false, + owner, + repo: repository, + security_and_analysis: { + secret_scanning: { + status: "enabled", + }, + secret_scanning_push_protection: { + status: "enabled", + }, + }, + squash_merge_commit_message: "PR_BODY", + squash_merge_commit_title: "PR_TITLE", + }); +} diff --git a/src/steps/removeSetupScripts.ts b/src/steps/removeSetupScripts.ts new file mode 100644 index 00000000..5ef90498 --- /dev/null +++ b/src/steps/removeSetupScripts.ts @@ -0,0 +1,21 @@ +import fs from "node:fs/promises"; + +const globPaths = [ + "./bin", + "./docs", + "./script", + "./src/bin", + "./src/create", + "./src/initialize", + "./src/migrate", + "./src/shared", + "./src/steps", + ".github/workflows/test-initialize.yml", + ".github/workflows/test-migrate.yml", +]; + +export async function removeSetupScripts() { + for (const globPath of globPaths) { + await fs.rm(globPath, { force: true, recursive: true }); + } +} diff --git a/src/steps/resetGitTags.ts b/src/steps/resetGitTags.ts new file mode 100644 index 00000000..59894fb5 --- /dev/null +++ b/src/steps/resetGitTags.ts @@ -0,0 +1,13 @@ +import { $ } from "execa"; + +export async function resetGitTags() { + const { stdout: allLocalTags } = await $`git tag -l`; + + // Create array of local tags by splitting the string at each new line and filtering out empty strings + const allLocalTagsArray = allLocalTags.split("\n").filter(Boolean); + + // Delete all local tags if there are any + if (allLocalTagsArray.length !== 0) { + await $`git tag -d ${allLocalTagsArray}`; + } +} diff --git a/src/steps/runCommands.test.ts b/src/steps/runCommands.test.ts new file mode 100644 index 00000000..90ffd6ea --- /dev/null +++ b/src/steps/runCommands.test.ts @@ -0,0 +1,71 @@ +import chalk from "chalk"; +import { describe, expect, it, vi } from "vitest"; + +import { runCommands } from "./runCommands.js"; + +const mockExecaCommand = vi.fn().mockRejectedValue("Oh no!"); + +vi.mock("execa", () => ({ + get execaCommand() { + return mockExecaCommand; + }, +})); + +const mockLogLine = vi.fn(); + +vi.mock("../shared/cli/lines.js", () => ({ + get logLine() { + return mockLogLine; + }, +})); + +vi.mock("../shared/cli/spinners.js", () => ({ + withSpinner: vi.fn((_label: string, callback: () => unknown) => callback()), +})); + +describe("runCommands", () => { + it("does not log when the commands all succeed", async () => { + mockExecaCommand.mockResolvedValue(undefined); + + await runCommands("label", ["first", "second"]); + + expect(mockLogLine).not.toHaveBeenCalled(); + }); + + it("logs once when one command fails", async () => { + mockExecaCommand + .mockRejectedValueOnce(new Error("Oh no!")) + .mockResolvedValue(undefined); + + await runCommands("label", ["first", "second"]); + + expect(mockLogLine).toHaveBeenCalledWith( + [ + chalk.yellow(`🟡 Running \``), + chalk.yellowBright("first"), + chalk.yellow(`\` failed. You should run it and fix its complaints.`), + ].join(""), + ); + }); + + it("logs twice when two commands fail", async () => { + mockExecaCommand.mockRejectedValue(new Error("Oh no!")); + + await runCommands("label", ["first", "second"]); + + expect(mockLogLine).toHaveBeenCalledWith( + [ + chalk.yellow(`🟡 Running \``), + chalk.yellowBright("first"), + chalk.yellow(`\` failed. You should run it and fix its complaints.`), + ].join(""), + ); + expect(mockLogLine).toHaveBeenCalledWith( + [ + chalk.yellow(`🟡 Running \``), + chalk.yellowBright("second"), + chalk.yellow(`\` failed. You should run it and fix its complaints.`), + ].join(""), + ); + }); +}); diff --git a/src/steps/runCommands.ts b/src/steps/runCommands.ts new file mode 100644 index 00000000..328e57a9 --- /dev/null +++ b/src/steps/runCommands.ts @@ -0,0 +1,35 @@ +import chalk from "chalk"; +import { execaCommand } from "execa"; + +import { logLine } from "../shared/cli/lines.js"; +import { withSpinner } from "../shared/cli/spinners.js"; + +export async function runCommands(label: string, commands: string[]) { + const failed: string[] = []; + + await withSpinner(label, async () => { + for (const command of commands) { + try { + await execaCommand(command); + } catch { + failed.push(command); + } + } + }); + + if (!failed.length) { + return; + } + + logLine(); + + for (const command of failed) { + logLine( + [ + chalk.yellow(`🟡 Running \``), + chalk.yellowBright(command), + chalk.yellow(`\` failed. You should run it and fix its complaints.`), + ].join(""), + ); + } +} diff --git a/src/steps/uninstallPackages.test.ts b/src/steps/uninstallPackages.test.ts new file mode 100644 index 00000000..28c03284 --- /dev/null +++ b/src/steps/uninstallPackages.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it, vi } from "vitest"; + +import { uninstallPackages } from "./uninstallPackages.js"; + +const mock$ = vi.fn(); + +vi.mock("execa", () => ({ + get $() { + return mock$; + }, +})); + +vi.mock("../shared/packages.js", () => ({ + readPackageData: () => ({}), + removeDependencies: vi.fn(), +})); + +describe("uninstallPackages", () => { + it("runs without --offline when offline is false", async () => { + await uninstallPackages(false); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "pnpm add prettier -D", + "", + ], + "", + ], + ] + `); + }); + + it("runs with --offline when offline is true", async () => { + await uninstallPackages(true); + + expect(mock$.mock.calls).toMatchInlineSnapshot(` + [ + [ + [ + "pnpm add prettier -D", + "", + ], + " --offline", + ], + ] + `); + }); +}); diff --git a/src/steps/uninstallPackages.ts b/src/steps/uninstallPackages.ts new file mode 100644 index 00000000..df5a62f6 --- /dev/null +++ b/src/steps/uninstallPackages.ts @@ -0,0 +1,46 @@ +import { $ } from "execa"; + +import { readPackageData, removeDependencies } from "../shared/packages.js"; + +export async function uninstallPackages(offline: boolean | undefined) { + const packageData = await readPackageData(); + + await removeDependencies( + [ + "@clack/prompts", + "all-contributors-for-repository", + "chalk", + "execa", + "git-remote-origin-url", + "git-url-parse", + "lazy-value", + "js-yaml", + "npm-user", + "octokit", + "prettier", + "replace-in-file", + "title-case", + "zod", + "zod-validation-error", + ], + packageData.dependencies, + ); + + await removeDependencies( + [ + "@octokit/request-error", + "@types/git-url-parse", + "@types/js-yaml", + "@types/prettier", + "all-contributors-cli", + "c8", + "eslint-config-prettier", + "globby", + "tsx", + ], + packageData.devDependencies, + "-D", + ); + + await $`pnpm add prettier -D${offline ? " --offline" : ""}`; +} diff --git a/src/steps/updateAllContributorsTable.ts b/src/steps/updateAllContributorsTable.ts new file mode 100644 index 00000000..5755b1a8 --- /dev/null +++ b/src/steps/updateAllContributorsTable.ts @@ -0,0 +1,27 @@ +import { $ } from "execa"; +import fs from "node:fs/promises"; +import prettier from "prettier"; + +import { readFileSafeAsJson } from "../shared/readFileSafeAsJson.js"; +import { AllContributorsData, Options } from "../shared/types.js"; + +export async function updateAllContributorsTable({ + owner, + repository, +}: Pick) { + await fs.writeFile( + ".all-contributorsrc", + await prettier.format( + JSON.stringify({ + ...((await readFileSafeAsJson( + ".all-contributorsrc", + )) as AllContributorsData), + projectName: repository, + projectOwner: owner, + }), + { parser: "json" }, + ), + ); + + await $`npx -y all-contributors-cli generate`; +} diff --git a/src/steps/updateLocalFiles.test.ts b/src/steps/updateLocalFiles.test.ts new file mode 100644 index 00000000..20d67399 --- /dev/null +++ b/src/steps/updateLocalFiles.test.ts @@ -0,0 +1,445 @@ +import { describe, expect, it, vi } from "vitest"; + +import { Options } from "../shared/types.js"; +import { updateLocalFiles } from "./updateLocalFiles.js"; + +const mockReplaceInFile = vi.fn(); + +vi.mock("replace-in-file", () => ({ + get default() { + return mockReplaceInFile; + }, +})); + +const mockReadFileSafeAsJson = vi.fn(); + +vi.mock("../shared/readFileSafeAsJson.js", () => ({ + get readFileSafeAsJson() { + return mockReadFileSafeAsJson; + }, +})); + +const options = { + access: "public", + author: undefined, + base: "everything", + createRepository: undefined, + description: "Stub description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintSpelling: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + logo: undefined, + mode: "create", + offline: true, + owner: "StubOwner", + repository: "stub-repository", + skipGitHubApi: false, + skipInstall: undefined, + skipRemoval: undefined, + skipRestore: undefined, + skipUninstall: undefined, + title: "Stub Title", +} satisfies Options; + +describe("updateLocalFiles", () => { + it("throws a wrapping error when replaceInFiles rejects", async () => { + const error = new Error("Oh no!"); + + mockReadFileSafeAsJson.mockResolvedValue({}); + mockReplaceInFile.mockRejectedValue(error); + + await expect(async () => { + await updateLocalFiles({ ...options, mode: "initialize" }); + }).rejects.toThrowErrorMatchingInlineSnapshot( + '"Failed to replace /Create TypeScript App/g with Stub Title in ./.github/**/*,./*.*"', + ); + }); + + it("replaces using the common replacements when the existing package data is null", async () => { + mockReadFileSafeAsJson.mockResolvedValue(null); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "initialize" }); + + expect(mockReplaceInFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "files": [ + "./.github/**/*", + "./*.*", + ], + "from": /Create TypeScript App/g, + "to": "Stub Title", + }, + ], + [ + { + "files": [ + "./.github/**/*", + "./*.*", + ], + "from": /JoshuaKGoldberg\\(\\?!\\\\/console-fail-test\\)/g, + "to": "StubOwner", + }, + ], + [ + { + "files": [ + "./.github/**/*", + "./*.*", + ], + "from": /create-typescript-app/g, + "to": "stub-repository", + }, + ], + [ + { + "files": ".eslintrc.cjs", + "from": /\\\\/\\\\\\*\\\\n\\.\\+\\\\\\*\\\\/\\\\n\\\\n/gs, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"author": "\\.\\+"/g, + "to": "\\"author\\": \\"undefined\\"", + }, + ], + [ + { + "files": "./package.json", + "from": /"bin": "\\.\\+\\\\n/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"test:create": "\\.\\+\\\\n/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"test:initialize": "\\.\\*/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"initialize": "\\.\\*/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"test:migrate": "\\.\\+\\\\n/g, + "to": "", + }, + ], + [ + { + "files": "./README.md", + "from": /## Getting Started\\.\\*## Development/gs, + "to": "## Development", + }, + ], + [ + { + "files": "./.github/DEVELOPMENT.md", + "from": /\\\\n## Setup Scripts\\.\\*\\$/gs, + "to": "", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": " \\"src/initialize/index.ts\\", + ", + "to": "", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": " \\"src/migrate/index.ts\\", + ", + "to": "", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": "[\\"src/index.ts!\\", \\"script/initialize*.js\\"]", + "to": "\\"src/index.ts!\\"", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": "[\\"src/**/*.ts!\\", \\"script/**/*.js\\"]", + "to": "\\"src/**/*.ts!\\"", + }, + ], + [ + { + "files": "./README.md", + "from": "> 💙 This package is based on [@StubOwner](https://github.com/StubOwner)'s [stub-repository](https://github.com/JoshuaKGoldberg/stub-repository).", + "to": "> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).", + }, + ], + ] + `); + }); + + it("replaces using the common replacements when the existing package data is an empty object", async () => { + mockReadFileSafeAsJson.mockResolvedValue({}); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "initialize" }); + + expect(mockReplaceInFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + { + "files": [ + "./.github/**/*", + "./*.*", + ], + "from": /Create TypeScript App/g, + "to": "Stub Title", + }, + ], + [ + { + "files": [ + "./.github/**/*", + "./*.*", + ], + "from": /JoshuaKGoldberg\\(\\?!\\\\/console-fail-test\\)/g, + "to": "StubOwner", + }, + ], + [ + { + "files": [ + "./.github/**/*", + "./*.*", + ], + "from": /create-typescript-app/g, + "to": "stub-repository", + }, + ], + [ + { + "files": ".eslintrc.cjs", + "from": /\\\\/\\\\\\*\\\\n\\.\\+\\\\\\*\\\\/\\\\n\\\\n/gs, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"author": "\\.\\+"/g, + "to": "\\"author\\": \\"undefined\\"", + }, + ], + [ + { + "files": "./package.json", + "from": /"bin": "\\.\\+\\\\n/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"test:create": "\\.\\+\\\\n/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"test:initialize": "\\.\\*/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"initialize": "\\.\\*/g, + "to": "", + }, + ], + [ + { + "files": "./package.json", + "from": /"test:migrate": "\\.\\+\\\\n/g, + "to": "", + }, + ], + [ + { + "files": "./README.md", + "from": /## Getting Started\\.\\*## Development/gs, + "to": "## Development", + }, + ], + [ + { + "files": "./.github/DEVELOPMENT.md", + "from": /\\\\n## Setup Scripts\\.\\*\\$/gs, + "to": "", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": " \\"src/initialize/index.ts\\", + ", + "to": "", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": " \\"src/migrate/index.ts\\", + ", + "to": "", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": "[\\"src/index.ts!\\", \\"script/initialize*.js\\"]", + "to": "\\"src/index.ts!\\"", + }, + ], + [ + { + "files": "./knip.jsonc", + "from": "[\\"src/**/*.ts!\\", \\"script/**/*.js\\"]", + "to": "\\"src/**/*.ts!\\"", + }, + ], + [ + { + "files": "./README.md", + "from": "> 💙 This package is based on [@StubOwner](https://github.com/StubOwner)'s [stub-repository](https://github.com/JoshuaKGoldberg/stub-repository).", + "to": "> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).", + }, + ], + ] + `); + }); + + it("does not replace an existing description when it does not exist", async () => { + mockReadFileSafeAsJson.mockResolvedValue({}); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "initialize" }); + + expect(mockReplaceInFile).not.toHaveBeenCalledWith({ + files: ["./.github/**/*", "./*.*"], + from: expect.anything(), + to: options.description, + }); + }); + it("replaces an existing description when it exists", async () => { + const existingDescription = "Existing description."; + + mockReadFileSafeAsJson.mockResolvedValue({ + description: existingDescription, + }); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "initialize" }); + + expect(mockReplaceInFile).toHaveBeenCalledWith({ + files: ["./.github/**/*", "./*.*"], + from: existingDescription, + to: options.description, + }); + }); + + it("removes bin when the mode is initialize", async () => { + mockReadFileSafeAsJson.mockResolvedValue({ + version: "1.2.3", + }); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "initialize" }); + + expect(mockReplaceInFile).toHaveBeenCalledWith({ + files: "./package.json", + from: /"bin": ".+\n/g, + to: "", + }); + }); + + it("does not remove bin when the mode is migrate", async () => { + mockReadFileSafeAsJson.mockResolvedValue({ + version: "1.2.3", + }); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "migrate" }); + + expect(mockReplaceInFile).not.toHaveBeenCalledWith({ + files: "./package.json", + from: /"bin": ".+\n/g, + to: "", + }); + }); + + it("resets package version to 0.0.0 when mode is initialize", async () => { + mockReadFileSafeAsJson.mockResolvedValue({ + version: "1.2.3", + }); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "initialize" }); + + expect(mockReplaceInFile).toHaveBeenCalledWith({ + files: "./package.json", + from: /"version": "1.2.3"/g, + to: '"version": "0.0.0"', + }); + }); + + it("does not reset package version to 0.0.0 when mode is migrate", async () => { + mockReadFileSafeAsJson.mockResolvedValue({ + version: "1.2.3", + }); + mockReplaceInFile.mockResolvedValue([]); + + await updateLocalFiles({ ...options, mode: "migrate" }); + + expect(mockReplaceInFile).not.toHaveBeenCalledWith({ + files: "./package.json", + from: /"version": "1.2.3"/g, + to: '"version": "0.0.0"', + }); + }); +}); diff --git a/src/steps/updateLocalFiles.ts b/src/steps/updateLocalFiles.ts new file mode 100644 index 00000000..dc15fcc3 --- /dev/null +++ b/src/steps/updateLocalFiles.ts @@ -0,0 +1,71 @@ +import replaceInFile from "replace-in-file"; + +import { readFileSafeAsJson } from "../shared/readFileSafeAsJson.js"; +import { Options } from "../shared/types.js"; + +interface ExistingPackageData { + description?: string; + version?: string; +} + +export async function updateLocalFiles(options: Options) { + const existingPackage = ((await readFileSafeAsJson("./package.json")) ?? + {}) as ExistingPackageData; + + const replacements = [ + [/Create TypeScript App/g, options.title], + [/JoshuaKGoldberg(?!\/console-fail-test)/g, options.owner], + [/create-typescript-app/g, options.repository], + [/\/\*\n.+\*\/\n\n/gs, ``, ".eslintrc.cjs"], + [/"author": ".+"/g, `"author": "${options.author}"`, "./package.json"], + ...(options.mode === "migrate" + ? [] + : [[/"bin": ".+\n/g, ``, "./package.json"]]), + [/"test:create": ".+\n/g, ``, "./package.json"], + [/"test:initialize": ".*/g, ``, "./package.json"], + [/"initialize": ".*/g, ``, "./package.json"], + [/"test:migrate": ".+\n/g, ``, "./package.json"], + [/## Getting Started.*## Development/gs, `## Development`, "./README.md"], + [/\n## Setup Scripts.*$/gs, "", "./.github/DEVELOPMENT.md"], + [`\t\t"src/initialize/index.ts",\n`, ``, "./knip.jsonc"], + [`\t\t"src/migrate/index.ts",\n`, ``, "./knip.jsonc"], + [ + `["src/index.ts!", "script/initialize*.js"]`, + `"src/index.ts!"`, + "./knip.jsonc", + ], + [`["src/**/*.ts!", "script/**/*.js"]`, `"src/**/*.ts!"`, "./knip.jsonc"], + // Edge case: migration scripts will rewrite README.md attribution + [ + `> 💙 This package is based on [@${options.owner}](https://github.com/${options.owner})'s [${options.repository}](https://github.com/JoshuaKGoldberg/${options.repository}).`, + `> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).`, + "./README.md", + ], + ]; + + if (existingPackage.description) { + replacements.push([existingPackage.description, options.description]); + } + + if (options.mode === "initialize" && existingPackage.version) { + replacements.push([ + new RegExp(`"version": "${existingPackage.version}"`, "g"), + `"version": "0.0.0"`, + "./package.json", + ]); + } + + for (const [from, to, files = ["./.github/**/*", "./*.*"]] of replacements) { + try { + // @ts-expect-error -- https://github.com/microsoft/TypeScript/issues/54342 + await replaceInFile({ files, from, to }); + } catch (error) { + throw new Error( + `Failed to replace ${from.toString()} with ${to} in ${files.toString()}`, + { + cause: error, + }, + ); + } + } +} diff --git a/src/steps/updateReadme.test.ts b/src/steps/updateReadme.test.ts new file mode 100644 index 00000000..9696a569 --- /dev/null +++ b/src/steps/updateReadme.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it, vi } from "vitest"; + +import { updateReadme } from "./updateReadme.js"; + +const mockAppendFile = vi.fn(); + +vi.mock("node:fs/promises", () => ({ + default: { + get appendFile() { + return mockAppendFile; + }, + }, +})); + +const mockReadFileSafe = vi.fn(); + +vi.mock("../shared/readFileSafe.js", () => ({ + get readFileSafe() { + return mockReadFileSafe; + }, +})); + +describe("updateReadme", () => { + it("adds a notice when the file does not contain it already", async () => { + mockReadFileSafe.mockResolvedValue(""); + + await updateReadme(); + + expect(mockAppendFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + "./README.md", + " + + + > 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app). + ", + ], + ] + `); + }); + + it("doesn't adds a notice when the file contains it already", async () => { + mockReadFileSafe.mockResolvedValue( + "", + ); + + await updateReadme(); + + expect(mockAppendFile.mock.calls).toMatchInlineSnapshot("[]"); + }); +}); diff --git a/src/steps/updateReadme.ts b/src/steps/updateReadme.ts new file mode 100644 index 00000000..a9a86889 --- /dev/null +++ b/src/steps/updateReadme.ts @@ -0,0 +1,22 @@ +import fs from "node:fs/promises"; +import { EOL } from "node:os"; + +import { readFileSafe } from "../shared/readFileSafe.js"; + +const detectionLine = ``; + +export const endOfReadmeNotice = [ + ``, + detectionLine, + ``, + `> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).`, + ``, +].join(EOL); + +export async function updateReadme() { + const contents = await readFileSafe("./README.md", ""); + + if (!contents.includes(detectionLine)) { + await fs.appendFile("./README.md", endOfReadmeNotice); + } +} diff --git a/src/steps/writeReadme/findExistingBadges.test.ts b/src/steps/writeReadme/findExistingBadges.test.ts new file mode 100644 index 00000000..9d98caf1 --- /dev/null +++ b/src/steps/writeReadme/findExistingBadges.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, it, test } from "vitest"; + +import { findExistingBadges } from "./findExistingBadges.js"; + +describe("findExistingBadges", () => { + describe("no result cases", () => { + test.each([ + "", + "abc123", + "# Test Title", + "[]", + "[][]", + "[]()", + "[][]()()", + ``, + ])("%j", (input) => { + expect(findExistingBadges(input)).toEqual([]); + }); + }); + + describe("single result cases", () => { + test.each([ + `[![GitHub CI](https://github.com/JoshuaKGoldberg/console-fail-test/actions/workflows/compile.yml/badge.svg)](https://github.com/JoshuaKGoldberg/console-fail-test/actions/workflows/compile.yml)`, + `[![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](https://prettier.io)`, + `![TypeScript: Strict](https://img.shields.io/badge/typescript-strict-brightgreen.svg)`, + `[![NPM version](https://badge.fury.io/js/console-fail-test.svg)](http://badge.fury.io/js/console-fail-test)`, + `[![Downloads](http://img.shields.io/npm/dm/console-fail-test.svg)](https://npmjs.org/package/console-fail-test)`, + "badge", + "badge", + "badge", + "badge", + ` + + + All Contributors: 1 + + + `, + ` + Codecov Test Coverage + `, + ` + Contributor Covenant + `, + ` + + License: MIT + `, + ` + + Sponsor: On GitHub + `, + `Style: Prettier`, + `TypeScript: Strict`, + ])("%s", (contents) => { + expect(findExistingBadges(contents)).toEqual([contents.trim()]); + }); + }); + + it("doesn't collect badges after a ##", () => { + expect( + findExistingBadges(` + test badge a + + ## Usage + + test badge b + `), + ).toEqual([`test badge a`]); + }); + + it("doesn't collect badges after an h2", () => { + expect( + findExistingBadges(` + test badge a + +

Usage

+ + test badge b + `), + ).toEqual([`test badge a`]); + }); + + test("real-world usage", () => { + expect( + findExistingBadges(` +

+ + + +All Contributors: 1 + + + + + Codecov Test Coverage + + + Contributor Covenant + + + License: MIT + + + Sponsor: On GitHub + + Style: Prettier + TypeScript: Strict +

+ `), + ).toMatchInlineSnapshot(` + [ + " + + + \\"All + + + ", + " + \\"License: + ", + "\\"Codecov", + "\\"Sponsor:", + "\\"TypeScript:", + ] + `); + }); +}); diff --git a/src/steps/writeReadme/findExistingBadges.ts b/src/steps/writeReadme/findExistingBadges.ts new file mode 100644 index 00000000..454a604d --- /dev/null +++ b/src/steps/writeReadme/findExistingBadges.ts @@ -0,0 +1,33 @@ +export const existingBadgeMatcherCreators = [ + () => /\[!\[.+\]\(.+\)\]\(.+\)/g, + () => /!\[.+\]\(.+\)/g, + () => /^\s*[\s\S]+?<\/a>/gm, + () => //g, +]; + +export function findExistingBadges(contents: string): string[] { + const badges: string[] = []; + let remaining = contents.split(/<\s*h2.*>|##/)[0]; + + for (const createMatcher of existingBadgeMatcherCreators) { + const matcher = createMatcher(); + + while (true) { + const matched = matcher.exec(remaining); + + if (!matched) { + break; + } + + const [badge] = matched; + + badges.push(badge.trim()); + remaining = [ + remaining.slice(0, matched.index), + remaining.slice(matched.index + badge.length), + ].join(""); + } + } + + return badges; +} diff --git a/src/steps/writeReadme/findIntroSectionClose.test.ts b/src/steps/writeReadme/findIntroSectionClose.test.ts new file mode 100644 index 00000000..60da73e6 --- /dev/null +++ b/src/steps/writeReadme/findIntroSectionClose.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; + +import { findIntroSectionClose } from "./findIntroSectionClose.js"; + +describe("findIntroSectionClose", () => { + it.each([ + [ + `# First +## Second`, + 6, + ], + [ + `# First +

Second

`, + 6, + ], + [ + `# First +

Second

`, + 6, + ], + [ + `# First +\`\`\`js +... +\`\`\``, + 6, + ], + [ + `# First + +[![](https://img.shields.io/badge/abc-ffcc00.svg)](image.jpg) + +[![](https://img.shields.io/badge/abc-ffcc00.svg)](image.jpg) +`, + 135, + ], + [ + `Plain heading + +Next line. +`, + 14, + ], + ])("%s", (contents, expected) => { + expect(findIntroSectionClose(contents)).toEqual(expected); + }); +}); diff --git a/src/steps/writeReadme/findIntroSectionClose.ts b/src/steps/writeReadme/findIntroSectionClose.ts new file mode 100644 index 00000000..e910b5f3 --- /dev/null +++ b/src/steps/writeReadme/findIntroSectionClose.ts @@ -0,0 +1,22 @@ +import { existingBadgeMatcherCreators } from "./findExistingBadges.js"; + +export function findIntroSectionClose(contents: string) { + // Highest priority match: an h2, presumably following badges + const indexOfH2OrCodeBlock = contents.search(/## |<\s*h2|```/); + + if (indexOfH2OrCodeBlock !== -1) { + return indexOfH2OrCodeBlock - 2; + } + + // Failing that, if any badges are found, go after the last of them + for (const createMatcher of existingBadgeMatcherCreators) { + const lastMatch = [...contents.matchAll(createMatcher())].at(-1); + + if (lastMatch?.index) { + return lastMatch.index + lastMatch[0].length + 2; + } + } + + // Lastly, go for the second line altogether + return contents.indexOf("\n", 1) + 1; +} diff --git a/src/steps/writeReadme/generateTopContent.test.ts b/src/steps/writeReadme/generateTopContent.test.ts new file mode 100644 index 00000000..d85afee6 --- /dev/null +++ b/src/steps/writeReadme/generateTopContent.test.ts @@ -0,0 +1,205 @@ +import { describe, expect, it } from "vitest"; + +import { Options } from "../../shared/types.js"; +import { generateTopContent } from "./generateTopContent.js"; + +const optionsBase = { + access: "public", + author: undefined, + base: undefined, + createRepository: undefined, + description: "", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintSpelling: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + logo: undefined, + mode: "create", + owner: "", + repository: "", + skipGitHubApi: false, + skipInstall: undefined, + skipRemoval: undefined, + skipRestore: undefined, + skipUninstall: undefined, + title: "", +} satisfies Options; + +describe("findExistingBadges", () => { + it("generates full contents when there are no existing badges", () => { + expect(generateTopContent(optionsBase, [])).toMatchInlineSnapshot(` + "

+ +

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + \\"Style: + \\"TypeScript: + \\"npm +

+ + ## Usage + + \`\`\`shell + npm i + \`\`\` + \`\`\`ts + import { greet } from \\"\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\`" + `); + }); + + it("replaces existing contents when there is an existing known badge", () => { + expect( + generateTopContent(optionsBase, [ + `TypeScript: Strict`, + ]), + ).toMatchInlineSnapshot(` + "

+ +

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + \\"Style: + \\"TypeScript: + \\"npm +

+ + ## Usage + + \`\`\`shell + npm i + \`\`\` + \`\`\`ts + import { greet } from \\"\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\`" + `); + }); + + it("push existing badges to the end when there is an existing unknown badge", () => { + expect( + generateTopContent(optionsBase, [ + `Unknown Badge`, + ]), + ).toMatchInlineSnapshot(` + "

+ +

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + \\"Style: + \\"TypeScript: + \\"npm + \\"Unknown +

+ + ## Usage + + \`\`\`shell + npm i + \`\`\` + \`\`\`ts + import { greet } from \\"\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\`" + `); + }); + + it("does not include a greet section when the mode is migrate", () => { + expect(generateTopContent({ ...optionsBase, mode: "migrate" }, [])) + .toMatchInlineSnapshot(` + "

+ +

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + \\"Style: + \\"TypeScript: + \\"npm +

" + `); + }); +}); diff --git a/src/steps/writeReadme/generateTopContent.ts b/src/steps/writeReadme/generateTopContent.ts new file mode 100644 index 00000000..1c7d8b59 --- /dev/null +++ b/src/steps/writeReadme/generateTopContent.ts @@ -0,0 +1,102 @@ +import { Options } from "../../shared/types.js"; + +export function generateTopContent(options: Options, existingBadges: string[]) { + const remainingExistingBadges = new Set(existingBadges); + const badges: string[] = []; + + function spliceBadge( + badgeLine: false | string | undefined, + existingMatcher: RegExp, + ) { + const existingMatch = existingBadges.find((existingLine) => + existingMatcher.test(existingLine), + ); + + if (existingMatch) { + remainingExistingBadges.delete(existingMatch); + } + + if (badgeLine) { + badges.push(badgeLine); + } + } + + for (const [badgeLine, existingMatcher] of [ + [ + !options.excludeAllContributors && + ` + + +All Contributors: 2 + + +`, + /ALL-CONTRIBUTORS-BADGE:START/, + ], + [ + !options.excludeTests && + ` + Codecov Test Coverage + `, + /https:\/\/codecov\.io\/gh/, + ], + [ + ` + Contributor Covenant + `, + /CODE_OF_CONDUCT\.md/, + ], + [ + ` + License: MIT + `, + /LICENSE\.(md|txt)/, + ], + [ + options.funding && + ` + Sponsor: On GitHub + `, + /github.+sponsors/, + ], + [ + `Style: Prettier`, + /style.*prettier/i, + ], + [ + `TypeScript: Strict`, + /typescript.*strict/i, + ], + [ + `npm package version`, + /npm.*v/i, + ], + ] as const) { + spliceBadge(badgeLine, existingMatcher); + } + + return `

${options.title}

+ +

${options.description}

+ +

+${[...badges, ...remainingExistingBadges] + .map((badge) => `\t${badge}`) + .join("\n")} +

${ + options.mode === "migrate" + ? "" + : ` + +## Usage + +\`\`\`shell +npm i ${options.repository} +\`\`\` +\`\`\`ts +import { greet } from "${options.repository}"; + +greet("Hello, world! 💖"); +\`\`\`` + }`; +} diff --git a/src/steps/writeReadme/index.test.ts b/src/steps/writeReadme/index.test.ts new file mode 100644 index 00000000..d45f249c --- /dev/null +++ b/src/steps/writeReadme/index.test.ts @@ -0,0 +1,403 @@ +import { describe, expect, it, vi } from "vitest"; + +import { Options } from "../../shared/types.js"; +import { writeReadme } from "./index.js"; + +const mockWriteFile = vi.fn(); + +vi.mock("node:fs/promises", () => ({ + get default() { + return { + get writeFile() { + return mockWriteFile; + }, + }; + }, +})); + +const mockReadFileSafe = vi.fn(); + +vi.mock("../../shared/readFileSafe.js", () => ({ + get readFileSafe() { + return mockReadFileSafe; + }, +})); + +const options = { + access: "public", + author: "Test Author", + base: "everything", + createRepository: false, + description: "Test description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintSpelling: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: "TestFunding", + logo: undefined, + mode: "create", + owner: "TestOwner", + repository: "test-repository", + skipGitHubApi: false, + skipInstall: true, + skipRemoval: false, + skipRestore: false, + skipUninstall: false, + title: "Test Title", +} satisfies Options; + +describe("writeReadme", () => { + it("writes a new file when the README.md does not yet exist", async () => { + mockReadFileSafe.mockResolvedValueOnce(""); + + await writeReadme(options); + + expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + "README.md", + "

Test Title

+ +

Test description.

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + + \\"Sponsor: + + \\"Style: + \\"TypeScript: + \\"npm +

+ + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\` + + ## Contributors + + + + + + +
+ + + + + + + + + > 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app). + ", + ], + ] + `); + }); + + it("adds sections when the README.md already exists and is sparse", async () => { + mockReadFileSafe.mockResolvedValueOnce(`# ${options.title}\n`); + + await writeReadme(options); + + expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + "README.md", + "

Test Title

+ +

Test description.

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + + \\"Sponsor: + + \\"Style: + \\"TypeScript: + \\"npm +

+ + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\` + + ## Contributors + + + + + + +
+ + + + + + + + + > 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app). + ", + ], + ] + `); + }); + + it("adds all-contributors content when directed to and the indicator does not yet exist", async () => { + mockReadFileSafe.mockResolvedValueOnce(`# ${options.title}\n`); + + await writeReadme({ + ...options, + excludeAllContributors: false, + }); + + expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + "README.md", + "

Test Title

+ +

Test description.

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + + \\"Sponsor: + + \\"Style: + \\"TypeScript: + \\"npm +

+ + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\` + + ## Contributors + + + + + + +
+ + + + + + + + + > 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app). + ", + ], + ] + `); + }); + + it("does not duplicate sections when the README.md already exists and has them", async () => { + mockReadFileSafe.mockResolvedValueOnce(`

Test Title

+ +

Test description.

+ +

+ + + +All Contributors: 2 + + + + + + + Contributor Covenant + + + License: MIT + + Style: Prettier + TypeScript: Strict +

+ + +## Contributors + + + + + + + +
+ + + + + + + + + + + +> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app). +`); + + await writeReadme(options); + + expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(` + [ + [ + "README.md", + "

Test Title

+ +

Test description.

+ +

+ + + + \\"All + + + + + \\"Codecov + + + \\"Contributor + + + \\"License: + + + \\"Sponsor: + + \\"Style: + \\"TypeScript: + \\"npm + \\"Contributor +

+ + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from \\"test-repository\\"; + + greet(\\"Hello, world! 💖\\"); + \`\`\` + + ## Contributors + + + + + + + +
+ + + + + + + + + + + > 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app). + ", + ], + ] + `); + }); +}); diff --git a/src/steps/writeReadme/index.ts b/src/steps/writeReadme/index.ts new file mode 100644 index 00000000..26af70de --- /dev/null +++ b/src/steps/writeReadme/index.ts @@ -0,0 +1,73 @@ +import fs from "node:fs/promises"; + +import { readFileSafe } from "../../shared/readFileSafe.js"; +import { Options } from "../../shared/types.js"; +import { endOfReadmeNotice } from "../updateReadme.js"; +import { findExistingBadges } from "./findExistingBadges.js"; +import { findIntroSectionClose } from "./findIntroSectionClose.js"; +import { generateTopContent } from "./generateTopContent.js"; + +const contributorsIndicator = ``; + +function generateAllContributorsContent(options: Options) { + return [ + `## Contributors`, + ``, + ``, + contributorsIndicator, + ``, + !options.excludeLintMd && ``, + ``, + ``, + `
`, + ``, + !options.excludeLintMd && ``, + ``, + ``, + ``, + ``, + ] + .filter(Boolean) + .join("\n"); +} + +export async function writeReadme(options: Options) { + const allContributorsContent = + !options.excludeAllContributors && generateAllContributorsContent(options); + let contents = await readFileSafe("README.md", ""); + if (!contents) { + await fs.writeFile( + "README.md", + [ + generateTopContent(options, []), + allContributorsContent, + endOfReadmeNotice, + ] + .filter(Boolean) + .join("\n\n"), + ); + return; + } + + const endOfIntroSection = findIntroSectionClose(contents); + + contents = [ + generateTopContent(options, findExistingBadges(contents)), + contents.slice(endOfIntroSection), + ] + .join("") + .replace(/\[!\[.+\]\(.+\)\]\(.+\)/g, "") + .replace(/!\[.+\]\(.+\)/g, "") + .replaceAll("\r", "") + .replaceAll("\n\n\n", "\n\n"); + + if (allContributorsContent && !contents.includes(contributorsIndicator)) { + contents = [contents, allContributorsContent].join("\n\n"); + } + + if (!contents.includes(endOfReadmeNotice)) { + contents = [contents, endOfReadmeNotice].join("\n\n"); + } + + await fs.writeFile("README.md", contents); +} diff --git a/src/steps/writing/creation/createESLintRC.test.ts b/src/steps/writing/creation/createESLintRC.test.ts new file mode 100644 index 00000000..5eab2434 --- /dev/null +++ b/src/steps/writing/creation/createESLintRC.test.ts @@ -0,0 +1,274 @@ +import { describe, expect, it } from "vitest"; + +import { Options } from "../../../shared/types.js"; +import { createESLintRC } from "./createESLintRC.js"; + +function fakeOptions(getExcludeValue: (exclusionName: string) => boolean) { + return { + access: "public", + author: "TestAuthor", + base: "everything", + createRepository: true, + description: "Test description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + ...Object.fromEntries( + [ + "excludeCompliance", + "excludeAllContributors", + "excludeLintDeprecation", + "excludeLintESLint", + "excludeLintJSDoc", + "excludeLintJson", + "excludeLintKnip", + "excludeLintMd", + "excludeLintPackageJson", + "excludeLintPackages", + "excludeLintPerfectionist", + "excludeLintRegex", + "excludeLintSpelling", + "excludeLintStrict", + "excludeLintStylistic", + "excludeLintYml", + "excludeReleases", + "excludeRenovate", + "excludeTests", + ].map((key) => [key, getExcludeValue(key)]), + ), + logo: undefined, + mode: "create", + owner: "TestOwner", + repository: "test-repository", + skipGitHubApi: true, + skipInstall: true, + skipRemoval: true, + title: "Test Title", + } satisfies Options; +} + +describe("createESLintRC", () => { + it("creates a minimal config when all exclusions are enabled", async () => { + expect(await createESLintRC(fakeOptions(() => true))) + .toMatchInlineSnapshot(` + "/** @type {import(\\"@types/eslint\\").Linter.Config} */ + module.exports = { + env: { + es2022: true, + node: true, + }, + extends: [\\"eslint:recommended\\", \\"plugin:n/recommended\\"], + overrides: [ + { + extends: [\\"plugin:@typescript-eslint/recommended\\"], + files: [\\"**/*.ts\\"], + parser: \\"@typescript-eslint/parser\\", + rules: { + // These off-by-default rules work well for this repo and we like them on. + \\"logical-assignment-operators\\": [ + \\"error\\", + \\"always\\", + { enforceForIfStatements: true }, + ], + \\"operator-assignment\\": \\"error\\", + }, + }, + { + files: \\"**/*.md/*.ts\\", + rules: { + \\"n/no-missing-import\\": [ + \\"error\\", + { allowModules: [\\"create-typescript-app\\"] }, + ], + }, + }, + { + extends: [\\"plugin:@typescript-eslint/recommended-type-checked\\"], + files: [\\"**/*.ts\\"], + parser: \\"@typescript-eslint/parser\\", + parserOptions: { + project: \\"./tsconfig.eslint.json\\", + }, + }, + ], + parser: \\"@typescript-eslint/parser\\", + plugins: [\\"@typescript-eslint\\"], + reportUnusedDisableDirectives: true, + root: true, + rules: { + // These off/less-strict-by-default rules work well for this repo and we like them on. + \\"@typescript-eslint/no-unused-vars\\": [\\"error\\", { caughtErrors: \\"all\\" }], + + // These on-by-default rules don't work well for this repo and we like them off. + \\"no-case-declarations\\": \\"off\\", + \\"no-constant-condition\\": \\"off\\", + \\"no-inner-declarations\\": \\"off\\", + \\"no-mixed-spaces-and-tabs\\": \\"off\\", + }, + }; + " + `); + }); + + it("creates a full config when all exclusions are disabled", async () => { + expect(await createESLintRC(fakeOptions(() => false))) + .toMatchInlineSnapshot(` + "/** @type {import(\\"@types/eslint\\").Linter.Config} */ + module.exports = { + env: { + es2022: true, + node: true, + }, + extends: [ + \\"eslint:recommended\\", + \\"plugin:eslint-comments/recommended\\", + \\"plugin:n/recommended\\", + \\"plugin:perfectionist/recommended-natural\\", + \\"plugin:regexp/recommended\\", + \\"plugin:vitest/recommended\\", + ], + overrides: [ + { + extends: [\\"plugin:markdown/recommended\\"], + files: [\\"**/*.md\\"], + processor: \\"markdown/markdown\\", + }, + { + extends: [ + \\"plugin:jsdoc/recommended-typescript-error\\", + \\"plugin:@typescript-eslint/strict\\", + \\"plugin:@typescript-eslint/stylistic\\", + ], + files: [\\"**/*.ts\\"], + parser: \\"@typescript-eslint/parser\\", + rules: { + // These off-by-default rules work well for this repo and we like them on. + \\"jsdoc/informative-docs\\": \\"error\\", + \\"logical-assignment-operators\\": [ + \\"error\\", + \\"always\\", + { enforceForIfStatements: true }, + ], + \\"operator-assignment\\": \\"error\\", + + // These on-by-default rules don't work well for this repo and we like them off. + \\"jsdoc/require-jsdoc\\": \\"off\\", + \\"jsdoc/require-param\\": \\"off\\", + \\"jsdoc/require-property\\": \\"off\\", + \\"jsdoc/require-returns\\": \\"off\\", + }, + }, + { + files: \\"**/*.md/*.ts\\", + rules: { + \\"n/no-missing-import\\": [ + \\"error\\", + { allowModules: [\\"create-typescript-app\\"] }, + ], + }, + }, + { + excludedFiles: [\\"**/*.md/*.ts\\"], + extends: [ + \\"plugin:@typescript-eslint/strict-type-checked\\", + \\"plugin:@typescript-eslint/stylistic-type-checked\\", + ], + files: [\\"**/*.ts\\"], + parser: \\"@typescript-eslint/parser\\", + parserOptions: { + project: \\"./tsconfig.eslint.json\\", + }, + rules: { + // These off-by-default rules work well for this repo and we like them on. + \\"deprecation/deprecation\\": \\"error\\", + }, + }, + { + excludedFiles: [\\"package.json\\"], + extends: [\\"plugin:jsonc/recommended-with-json\\"], + files: [\\"*.json\\", \\"*.jsonc\\"], + parser: \\"jsonc-eslint-parser\\", + rules: { + \\"jsonc/sort-keys\\": \\"error\\", + }, + }, + { + files: [\\"*.jsonc\\"], + rules: { + \\"jsonc/no-comments\\": \\"off\\", + }, + }, + { + files: \\"**/*.test.ts\\", + rules: { + // These on-by-default rules aren't useful in test files. + \\"@typescript-eslint/no-unsafe-assignment\\": \\"off\\", + \\"@typescript-eslint/no-unsafe-call\\": \\"off\\", + }, + }, + { + extends: [\\"plugin:yml/standard\\", \\"plugin:yml/prettier\\"], + files: [\\"**/*.{yml,yaml}\\"], + parser: \\"yaml-eslint-parser\\", + rules: { + \\"yml/file-extension\\": [\\"error\\", { extension: \\"yml\\" }], + \\"yml/sort-keys\\": [ + \\"error\\", + { + order: { type: \\"asc\\" }, + pathPattern: \\"^.*$\\", + }, + ], + \\"yml/sort-sequence-values\\": [ + \\"error\\", + { + order: { type: \\"asc\\" }, + pathPattern: \\"^.*$\\", + }, + ], + }, + }, + ], + parser: \\"@typescript-eslint/parser\\", + plugins: [ + \\"@typescript-eslint\\", + \\"deprecation\\", + \\"jsdoc\\", + \\"no-only-tests\\", + \\"perfectionist\\", + \\"regexp\\", + \\"vitest\\", + ], + reportUnusedDisableDirectives: true, + root: true, + rules: { + // These off/less-strict-by-default rules work well for this repo and we like them on. + \\"@typescript-eslint/no-unused-vars\\": [\\"error\\", { caughtErrors: \\"all\\" }], + \\"no-only-tests/no-only-tests\\": \\"error\\", + + // These on-by-default rules don't work well for this repo and we like them off. + \\"no-case-declarations\\": \\"off\\", + \\"no-constant-condition\\": \\"off\\", + \\"no-inner-declarations\\": \\"off\\", + \\"no-mixed-spaces-and-tabs\\": \\"off\\", + + // Stylistic concerns that don't interfere with Prettier + \\"@typescript-eslint/padding-line-between-statements\\": [ + \\"error\\", + { blankLine: \\"always\\", next: \\"*\\", prev: \\"block-like\\" }, + ], + \\"perfectionist/sort-objects\\": [ + \\"error\\", + { + order: \\"asc\\", + \\"partition-by-comment\\": true, + type: \\"natural\\", + }, + ], + }, + }; + " + `); + }); +}); diff --git a/src/steps/writing/creation/createESLintRC.ts b/src/steps/writing/creation/createESLintRC.ts new file mode 100644 index 00000000..d2cea501 --- /dev/null +++ b/src/steps/writing/creation/createESLintRC.ts @@ -0,0 +1,231 @@ +import { Options } from "../../../shared/types.js"; +import { formatTypeScript } from "./formatters/formatTypeScript.js"; + +export async function createESLintRC(options: Options) { + return await formatTypeScript(`/** @type {import("@types/eslint").Linter.Config} */ +module.exports = { + env: { + es2022: true, + node: true, + }, + extends: [ + "eslint:recommended", + ${ + options.excludeLintESLint + ? "" + : `"plugin:eslint-comments/recommended", + ` + }"plugin:n/recommended",${ + options.excludeLintPerfectionist + ? "" + : ` + "plugin:perfectionist/recommended-natural",` + }${options.excludeLintRegex ? "" : `"plugin:regexp/recommended",`}${ + options.excludeTests ? "" : `"plugin:vitest/recommended",` + } + ], + overrides: [${ + options.excludeLintMd + ? "" + : ` + { + extends: ["plugin:markdown/recommended"], + files: ["**/*.md"], + processor: "markdown/markdown", + },` + } + { + extends: [ + ${ + options.excludeLintJSDoc + ? "" + : `"plugin:jsdoc/recommended-typescript-error", + ` + }"plugin:@typescript-eslint/${ + options.excludeLintStrict ? "recommended" : "strict" + }",${ + options.excludeLintStylistic + ? "" + : ` + "plugin:@typescript-eslint/stylistic",` + } + ], + files: ["**/*.ts"], + parser: "@typescript-eslint/parser", + rules: { + // These off-by-default rules work well for this repo and we like them on. + ${ + options.excludeLintJSDoc + ? "" + : `"jsdoc/informative-docs": "error", + ` + }"logical-assignment-operators": [ + "error", + "always", + { enforceForIfStatements: true }, + ], + "operator-assignment": "error",${ + options.excludeLintJSDoc + ? "" + : ` + + // These on-by-default rules don't work well for this repo and we like them off. + "jsdoc/require-jsdoc": "off", + "jsdoc/require-param": "off", + "jsdoc/require-property": "off", + "jsdoc/require-returns": "off",` + } + }, + }, + { + files: "**/*.md/*.ts", + rules: { + "n/no-missing-import": [ + "error", + { allowModules: ["create-typescript-app"] }, + ], + }, + }, + { + ${ + options.excludeLintMd + ? "" + : `excludedFiles: ["**/*.md/*.ts"], + ` + }extends: [ + "plugin:@typescript-eslint/${ + options.excludeLintStrict ? "recommended" : "strict" + }-type-checked",${ + options.excludeLintStylistic + ? "" + : ` + "plugin:@typescript-eslint/stylistic-type-checked",` + } + ], + files: ["**/*.ts"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.eslint.json", + },${ + options.excludeLintDeprecation + ? "" + : `rules: { + // These off-by-default rules work well for this repo and we like them on. + "deprecation/deprecation": "error", + },` + } + }, + ${ + options.excludeLintJson + ? "" + : `{ + excludedFiles: ["package.json"], + extends: ["plugin:jsonc/recommended-with-json"], + files: ["*.json", "*.jsonc"], + parser: "jsonc-eslint-parser", + rules: { + "jsonc/sort-keys": "error", + }, + }, + { + files: ["*.jsonc"], + rules: { + "jsonc/no-comments": "off", + }, + },` + }${ + options.excludeTests + ? "" + : `\n{ + files: "**/*.test.ts", + rules: { + // These on-by-default rules aren't useful in test files. + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + }, + },` + }${ + options.excludeLintYml + ? "" + : `\n{ + extends: ["plugin:yml/standard", "plugin:yml/prettier"], + files: ["**/*.{yml,yaml}"], + parser: "yaml-eslint-parser", + rules: { + "yml/file-extension": ["error", { extension: "yml" }], + "yml/sort-keys": [ + "error", + { + order: { type: "asc" }, + pathPattern: "^.*$", + }, + ], + "yml/sort-sequence-values": [ + "error", + { + order: { type: "asc" }, + pathPattern: "^.*$", + }, + ], + }, + }, + ` + }], + parser: "@typescript-eslint/parser", + plugins: [ + "@typescript-eslint", + ${ + options.excludeLintDeprecation + ? "" + : `"deprecation", + ` + }${ + options.excludeLintJSDoc + ? "" + : `"jsdoc", + ` + }${options.excludeTests ? "" : `"no-only-tests",`}${ + options.excludeLintPerfectionist ? "" : `"perfectionist",` + }${options.excludeLintRegex ? "" : `"regexp",`}${ + options.excludeTests ? "" : `\n"vitest",` + } + ], + reportUnusedDisableDirectives: true, + root: true, + rules: { + // These off/less-strict-by-default rules work well for this repo and we like them on. + "@typescript-eslint/no-unused-vars": ["error", { caughtErrors: "all" }],${ + options.excludeTests ? "" : `\n"no-only-tests/no-only-tests": "error",` + } + + // These on-by-default rules don't work well for this repo and we like them off. + "no-case-declarations": "off", + "no-constant-condition": "off", + "no-inner-declarations": "off", + "no-mixed-spaces-and-tabs": "off", + + ${ + options.excludeLintStylistic + ? "" + : `// Stylistic concerns that don't interfere with Prettier + "@typescript-eslint/padding-line-between-statements": [ + "error", + { blankLine: "always", next: "*", prev: "block-like" }, + ], + ` + }${ + options.excludeLintPerfectionist + ? "" + : `"perfectionist/sort-objects": [ + "error", + { + order: "asc", + "partition-by-comment": true, + type: "natural", + }, + ],` + } + }, +}; +`); +} diff --git a/src/steps/writing/creation/dotGitHub/actions.test.ts b/src/steps/writing/creation/dotGitHub/actions.test.ts new file mode 100644 index 00000000..5650f7c4 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/actions.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; + +import { createDotGitHubActions } from "./actions.js"; + +describe("createDotGitHubActions", () => { + it("creates a prepare/action.yml file", () => { + const actual = createDotGitHubActions(); + + expect(actual).toEqual({ + prepare: { + "action.yml": `description: Prepares the repo for a typical CI job + +name: Prepare + +runs: + steps: + - uses: pnpm/action-setup@v2 + - uses: actions/setup-node@v3 + with: + cache: pnpm + node-version: '18' + - run: pnpm install --frozen-lockfile + shell: bash + using: composite +`, + }, + }); + }); +}); diff --git a/src/steps/writing/creation/dotGitHub/actions.ts b/src/steps/writing/creation/dotGitHub/actions.ts new file mode 100644 index 00000000..67db8be3 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/actions.ts @@ -0,0 +1,28 @@ +import jsYaml from "js-yaml"; + +export function createDotGitHubActions() { + return { + prepare: { + "action.yml": jsYaml + .dump({ + description: "Prepares the repo for a typical CI job", + name: "Prepare", + runs: { + steps: [ + { uses: "pnpm/action-setup@v2" }, + { + uses: "actions/setup-node@v3", + with: { cache: "pnpm", "node-version": "18" }, + }, + { + run: "pnpm install --frozen-lockfile", + shell: "bash", + }, + ], + using: "composite", + }, + }) + .replaceAll(/\n(\S)/g, "\n\n$1"), + }, + }; +} diff --git a/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts b/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts new file mode 100644 index 00000000..02a2779d --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createDevelopment.test.ts @@ -0,0 +1,259 @@ +import { describe, expect, it } from "vitest"; + +import { Options } from "../../../../shared/types.js"; +import { createDevelopment } from "./createDevelopment.js"; + +const options = { + access: "public", + author: "Test Author", + base: "everything", + createRepository: false, + description: "Test description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintSpelling: undefined, + excludeLintYml: undefined, + excludeReleases: undefined, + excludeRenovate: undefined, + excludeTests: undefined, + funding: undefined, + logo: undefined, + mode: "create", + owner: "TestOwner", + repository: "test-repository", + skipGitHubApi: false, + skipInstall: true, + skipRemoval: false, + skipRestore: false, + skipUninstall: false, + title: "Test Title", +} satisfies Options; + +describe("createDevelopment", () => { + it("creates a file with no extra linters when options exclude them", () => { + const actual = createDevelopment({ + ...options, + excludeLintKnip: false, + excludeLintMd: false, + excludeLintPackageJson: false, + excludeLintPackages: false, + excludeLintSpelling: false, + }); + + expect(actual).toMatchInlineSnapshot(` + "# Development + + After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + + \`\`\`shell + git clone https://github.com//test-repository + cd test-repository + pnpm install + \`\`\` + + > This repository includes a list of suggested VS Code extensions. + > It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development. + + ## Building + + Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`: + + \`\`\`shell + pnpm build + \`\`\` + + Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files: + + \`\`\`shell + pnpm build --watch + \`\`\` + + ## Formatting + + [Prettier](https://prettier.io) is used to format code. + It should be applied automatically when you save files in VS Code or make a Git commit. + + To manually reformat all files, you can run: + + \`\`\`shell + pnpm format --write + \`\`\` + + ## Linting + + This package includes several forms of linting to enforce consistent code quality and styling. + Each should be shown in VS Code, and can be run manually on the command-line: + + - \`pnpm lint\` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files + - \`pnpm lint:knip\` ([knip](https://github.com/webpro/knip)): Detects unused files, dependencies, and code exports + - \`pnpm lint:md\` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files + - \`pnpm lint:package-json\` ([npm-package-json-lint](https://npmpackagejsonlint.org/)): Lints the \`package.json\` file + - \`pnpm lint:packages\` ([pnpm dedupe --check](https://pnpm.io/cli/dedupe)): Checks for unnecessarily duplicated packages in the \`pnpm-lock.yml\` file + - \`pnpm lint:spelling\` ([cspell](https://cspell.org)): Spell checks across all source files + + Read the individual documentation for each linter to understand how it can be configured and used best. + + For example, ESLint can be run with \`--fix\` to auto-fix some lint rule complaints: + + \`\`\`shell + pnpm run lint --fix + \`\`\` + + ## Testing + + [Vitest](https://vitest.dev) is used for tests. + You can run it locally on the command-line: + + \`\`\`shell + pnpm run test + \`\`\` + + Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory: + + \`\`\`shell + pnpm run test --coverage + \`\`\` + + Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs. + Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail. + + ### Debugging Tests + + This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests. + To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5). + + ## Type Checking + + You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + + However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`: + + \`\`\`shell + pnpm tsc + \`\`\` + + Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files: + + \`\`\`shell + pnpm tsc --watch + \`\`\` + " + `); + }); + + it("creates a file with all extra linters when options include them", () => { + const actual = createDevelopment({ + ...options, + excludeLintKnip: true, + excludeLintMd: true, + excludeLintPackageJson: true, + excludeLintPackages: true, + excludeLintSpelling: true, + }); + + expect(actual).toMatchInlineSnapshot(` + "# Development + + After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + + \`\`\`shell + git clone https://github.com//test-repository + cd test-repository + pnpm install + \`\`\` + + > This repository includes a list of suggested VS Code extensions. + > It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development. + + ## Building + + Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`: + + \`\`\`shell + pnpm build + \`\`\` + + Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files: + + \`\`\`shell + pnpm build --watch + \`\`\` + + ## Formatting + + [Prettier](https://prettier.io) is used to format code. + It should be applied automatically when you save files in VS Code or make a Git commit. + + To manually reformat all files, you can run: + + \`\`\`shell + pnpm format --write + \`\`\` + + ## Linting + + [ESLint](https://eslint.org) is used with with [typescript-eslint](https://typescript-eslint.io)) to lint JavaScript and TypeScript source files. + You can run it locally on the command-line: + + \`\`\`shell + pnpm run lint + \`\`\` + + ESLint can be run with \`--fix\` to auto-fix some lint rule complaints: + + \`\`\`shell + pnpm run lint --fix + \`\`\` + + ## Testing + + [Vitest](https://vitest.dev) is used for tests. + You can run it locally on the command-line: + + \`\`\`shell + pnpm run test + \`\`\` + + Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory: + + \`\`\`shell + pnpm run test --coverage + \`\`\` + + Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs. + Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail. + + ### Debugging Tests + + This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests. + To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5). + + ## Type Checking + + You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + + However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`: + + \`\`\`shell + pnpm tsc + \`\`\` + + Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files: + + \`\`\`shell + pnpm tsc --watch + \`\`\` + " + `); + }); +}); diff --git a/src/steps/writing/creation/dotGitHub/createDevelopment.ts b/src/steps/writing/creation/dotGitHub/createDevelopment.ts new file mode 100644 index 00000000..4cc16000 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createDevelopment.ts @@ -0,0 +1,126 @@ +import { Options } from "../../../../shared/types.js"; + +export function createDevelopment(options: Options) { + const lintLines = [ + !options.excludeLintKnip && + `- \`pnpm lint:knip\` ([knip](https://github.com/webpro/knip)): Detects unused files, dependencies, and code exports`, + !options.excludeLintMd && + `- \`pnpm lint:md\` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files`, + !options.excludeLintPackageJson && + `- \`pnpm lint:package-json\` ([npm-package-json-lint](https://npmpackagejsonlint.org/)): Lints the \`package.json\` file`, + !options.excludeLintPackages && + `- \`pnpm lint:packages\` ([pnpm dedupe --check](https://pnpm.io/cli/dedupe)): Checks for unnecessarily duplicated packages in the \`pnpm-lock.yml\` file`, + !options.excludeLintSpelling && + `- \`pnpm lint:spelling\` ([cspell](https://cspell.org)): Spell checks across all source files`, + ].filter(Boolean); + + return `# Development + +After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + +\`\`\`shell +git clone https://github.com//${options.repository} +cd ${options.repository} +pnpm install +\`\`\` + +> This repository includes a list of suggested VS Code extensions. +> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development. + +## Building + +Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`: + +\`\`\`shell +pnpm build +\`\`\` + +Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files: + +\`\`\`shell +pnpm build --watch +\`\`\` + +## Formatting + +[Prettier](https://prettier.io) is used to format code. +It should be applied automatically when you save files in VS Code or make a Git commit. + +To manually reformat all files, you can run: + +\`\`\`shell +pnpm format --write +\`\`\` + +## Linting + +${ + lintLines.length + ? [ + `This package includes several forms of linting to enforce consistent code quality and styling.`, + `Each should be shown in VS Code, and can be run manually on the command-line:`, + ``, + `- \`pnpm lint\` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files`, + ...lintLines, + ``, + `Read the individual documentation for each linter to understand how it can be configured and used best.`, + ``, + `For example, ESLint can be run with \`--fix\` to auto-fix some lint rule complaints:`, + ].join("\n") + : `[ESLint](https://eslint.org) is used with with [typescript-eslint](https://typescript-eslint.io)) to lint JavaScript and TypeScript source files. +You can run it locally on the command-line: + +\`\`\`shell +pnpm run lint +\`\`\` + +ESLint can be run with \`--fix\` to auto-fix some lint rule complaints:` +} + +\`\`\`shell +pnpm run lint --fix +\`\`\` + +${ + !options.excludeTests && + `## Testing + +[Vitest](https://vitest.dev) is used for tests. +You can run it locally on the command-line: + +\`\`\`shell +pnpm run test +\`\`\` + +Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory: + +\`\`\`shell +pnpm run test --coverage +\`\`\` + +Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs. +Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail. + +### Debugging Tests + +This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests. +To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5). +` +} +## Type Checking + +You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + +However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`: + +\`\`\`shell +pnpm tsc +\`\`\` + +Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files: + +\`\`\`shell +pnpm tsc --watch +\`\`\` +`; +} diff --git a/src/steps/writing/creation/dotGitHub/createDotGitHubFiles.ts b/src/steps/writing/creation/dotGitHub/createDotGitHubFiles.ts new file mode 100644 index 00000000..d9a2fb99 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createDotGitHubFiles.ts @@ -0,0 +1,290 @@ +/* spellchecker: disable */ +import { Options } from "../../../../shared/types.js"; +import { formatJson } from "../formatters/formatJson.js"; +import { formatYaml } from "../formatters/formatYaml.js"; +import { createDevelopment } from "./createDevelopment.js"; + +export async function createDotGitHubFiles(options: Options) { + return { + "CODE_OF_CONDUCT.md": `# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, +and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall +community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of +any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, +without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +${options.email.github}. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations +`, + "CONTRIBUTING.md": `# Contributing + +Thanks for your interest in contributing to \`${options.repository}\`! 💖 + +> After this page, see [DEVELOPMENT.md](./DEVELOPMENT.md) for local development instructions. + +## Code of Conduct + +This project contains a [Contributor Covenant code of conduct](./CODE_OF_CONDUCT.md) all contributors are expected to follow. + +## Reporting Issues + +Please do [report an issue on the issue tracker](https://github.com/${options.owner}/${options.repository}/issues/new/choose) if there's any bugfix, documentation improvement, or general enhancement you'd like to see in the repository! Please fully fill out all required fields in the most appropriate issue form. + +## Sending Contributions + +Sending your own changes as contribution is always appreciated! +There are two steps involved: + +1. [Finding an Issue](#finding-an-issue) +2. [Sending a Pull Request](#sending-a-pull-request) + +### Finding an Issue + +With the exception of very small typos, all changes to this repository generally need to correspond to an [open issue marked as \`accepting prs\` on the issue tracker](https://github.com/${options.owner}/${options.repository}/issues?q=is%3Aopen+is%3Aissue+label%3A%22accepting+prs%22). +If this is your first time contributing, consider searching for [unassigned issues that also have the \`good first issue\` label](https://github.com/${options.owner}/${options.repository}/issues?q=is%3Aopen+is%3Aissue+label%3A%22accepting+prs%22+label%3A%22good+first+issue%22+no%3Aassignee). +If the issue you'd like to fix isn't found on the issue, see [Reporting Issues](#reporting-issues) for filing your own (please do!). + +#### Issue Claiming + +We don't use any kind of issue claiming system. +We've found in the past that they result in accidental ["licked cookie"](https://devblogs.microsoft.com/oldnewthing/20091201-00/?p=15843) situations where contributors claim an issue but run out of time or energy trying before sending a PR. + +If an issue has been marked as \`accepting prs\` and an open PR does not exist, feel free to send a PR. +Please don't post comments asking for permission or stating you will work on an issue. + +### Sending a Pull Request + +Once you've identified an open issue accepting PRs that doesn't yet have a PR sent, you're free to send a pull request. +Be sure to fill out the pull request template's requested information -- otherwise your PR will likely be closed. + +PRs are also expected to have a title that adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0). +Only PR titles need to be in that format, not individual commits. +Don't worry if you get this wrong: you can always change the PR title after sending it. +Check [previously merged PRs](https://github.com/${options.owner}/${options.repository}/pulls?q=is%3Apr+is%3Amerged+-label%3Adependencies+) for reference. + +#### Draft PRs + +If you don't think your PR is ready for review, [set it as a draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#converting-a-pull-request-to-a-draft). +Draft PRs won't be reviewed. + +#### Granular PRs + +Please keep pull requests single-purpose: in other words, don't attempt to solve multiple unrelated problems in one pull request. +Send one PR per area of concern. +Multi-purpose pull requests are harder and slower to review, block all changes from being merged until the whole pull request is reviewed, and are difficult to name well with semantic PR titles. + +#### Pull Request Reviews + +When a PR is not in draft, it's considered ready for review. +Please don't manually \`@\` tag anybody to request review. +A maintainer will look at it when they're next able to. + +PRs should have passing [GitHub status checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) before review is requested (unless there are explicit questions asked in the PR about any failures). + +#### Asking Questions + +If you need help and/or have a question, posting a comment in the PR is a great way to do so. +There's no need to tag anybody individually. +One of us will drop by and help when we can. + +Please post comments as [line comments](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#adding-line-comments-to-a-pull-request) when possible, so that they can be threaded. +You can [resolve conversations](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#resolving-conversations) on your own when you feel they're resolved - no need to comment explicitly and/or wait for a maintainer. + +#### Requested Changes + +After a maintainer reviews your PR, they may request changes on it. +Once you've made those changes, [re-request review on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review). + +Please try not to force-push commits to PRs that have already been reviewed. +Doing so makes it harder to review the changes. +We squash merge all commits so there's no need to try to preserve Git history within a PR branch. + +Once you've addressed all our feedback by making code changes and/or started a followup discussion, [re-request review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review) from each maintainer whose feedback you addressed. + +Once all feedback is addressed and the PR is approved, we'll ensure the branch is up to date with \`main\` and merge it for you. + +#### Post-Merge Recognition + +Once your PR is merged, if you haven't yet been added to the [_Contributors_ table in the README.md](../README.md#contributors) for its [type of contribution](https://allcontributors.org/docs/en/emoji-key "Allcontributors emoji key"), you should be soon. +Please do ping the maintainer who merged your PR if that doesn't happen within 24 hours - it was likely an oversight on our end! + +## Emojis & Appreciation + +If you made it all the way to the end, bravo dear user, we love you. +Please include your favorite emoji in the bottom of your issues and PRs to signal to us that you did in fact read this file and are trying to conform to it as best as possible. +💖 is a good starter if you're not sure which to use. +`, + "DEVELOPMENT.md": createDevelopment(options), + ...(options.funding && { + "FUNDING.yml": formatYaml({ github: options.funding }), + }), + + "ISSUE_TEMPLATE.md": ` + + + + + +## Overview + +... +`, + "PULL_REQUEST_TEMPLATE.md": ` + +## PR Checklist + +- [ ] Addresses an existing open issue: fixes #000 +- [ ] That issue was marked as [\`status: accepting prs\`](https://github.com/${options.owner}/${options.repository}/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) +- [ ] Steps in [CONTRIBUTING.md](https://github.com/${options.owner}/${options.repository}/blob/main/.github/CONTRIBUTING.md) were taken + +## Overview + + +`, + "SECURITY.md": `# Security Policy + +We take all security vulnerabilities seriously. +If you have a vulnerability or other security issues to disclose: + +- Thank you very much, please do! +- Please send them to us by emailing \`${options.email.github}\` + +We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. +`, + ...(!options.excludeRenovate && { + "renovate.json": await formatJson({ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + automerge: true, + internalChecksFilter: "strict", + labels: ["dependencies"], + minimumReleaseAge: "3 days", + postUpdateOptions: ["pnpmDedupe"], + }), + }), + }; +} diff --git a/src/steps/writing/creation/dotGitHub/createWorkflowFile.test.ts b/src/steps/writing/creation/dotGitHub/createWorkflowFile.test.ts new file mode 100644 index 00000000..396a067e --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createWorkflowFile.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; + +import { createWorkflowFile } from "./createWorkflowFile.js"; + +describe("createWorkflowFile", () => { + it("creates a workflow file when runs are provided", () => { + const actual = createWorkflowFile({ + name: "Test Name", + runs: ["pnpm build"], + }); + + expect(actual).toBe( + `jobs: + test_name: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build + +name: Test Name + +on: + pull_request: ~ + push: + branches: + - main +`, + ); + }); +}); diff --git a/src/steps/writing/creation/dotGitHub/createWorkflowFile.ts b/src/steps/writing/creation/dotGitHub/createWorkflowFile.ts new file mode 100644 index 00000000..da7417d0 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createWorkflowFile.ts @@ -0,0 +1,98 @@ +import { formatYaml } from "../formatters/formatYaml.js"; + +interface WorkflowFileConcurrency { + "cancel-in-progress"?: boolean; + group: string; +} + +interface WorkflowFileOn { + pull_request?: + | { + branches?: string | string[]; + types?: string[]; + } + | null + | string; + pull_request_target?: { + types: string[]; + }; + push?: { + branches: string[]; + }; + release?: { + types: string[]; + }; + workflow_dispatch?: null | string; +} + +interface WorkflowFilePermissions { + contents?: string; + "id-token"?: string; + "pull-requests"?: string; +} + +interface WorkflowFileStep { + env?: Record; + if?: string; + name?: string; + run?: string; + uses?: string; + with?: Record; +} + +interface WorkflowFileOptionsBase { + concurrency?: WorkflowFileConcurrency; + name: string; + on?: WorkflowFileOn; + permissions?: WorkflowFilePermissions; +} + +interface WorkflowFileOptionsRuns extends WorkflowFileOptionsBase { + runs: (WorkflowFileStep | string)[]; +} + +interface WorkflowFileOptionsSteps extends WorkflowFileOptionsBase { + steps: WorkflowFileStep[]; +} + +type WorkflowFileOptions = WorkflowFileOptionsRuns | WorkflowFileOptionsSteps; + +export function createWorkflowFile({ + concurrency, + name, + on = { + pull_request: null, + push: { + branches: ["main"], + }, + }, + permissions, + ...options +}: WorkflowFileOptions) { + return ( + formatYaml({ + concurrency, + jobs: { + [name.replaceAll(" ", "_").toLowerCase()]: { + "runs-on": "ubuntu-latest", + steps: + "runs" in options + ? [ + { uses: "actions/checkout@v4" }, + { uses: "./.github/actions/prepare" }, + ...options.runs.map((run) => ({ run })), + ] + : options.steps, + }, + }, + name, + on, + permissions, + }) + .replaceAll(/\n(\S)/g, "\n\n$1") + // https://github.com/nodeca/js-yaml/pull/515 + .replaceAll(/: "\\n(.+)"/g, ": |\n$1") + .replaceAll("\\n", "\n") + .replaceAll("\\t", " ") + ); +} diff --git a/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts b/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts new file mode 100644 index 00000000..3a15093d --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts @@ -0,0 +1,494 @@ +import { describe, expect, it } from "vitest"; + +import { Options } from "../../../../shared/types.js"; +import { createWorkflows } from "./createWorkflows.js"; + +const createOptions = (exclude: boolean) => + ({ + access: "public", + author: undefined, + base: "everything", + description: "Stub description.", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: exclude, + excludeCompliance: exclude, + excludeLintJson: exclude, + excludeLintKnip: exclude, + excludeLintMd: exclude, + excludeLintPackageJson: exclude, + excludeLintPackages: exclude, + excludeLintPerfectionist: exclude, + excludeLintSpelling: exclude, + excludeLintYml: exclude, + excludeReleases: exclude, + excludeRenovate: exclude, + excludeTests: exclude, + funding: undefined, + logo: undefined, + mode: "create", + owner: "StubOwner", + repository: "stub-repository", + title: "Stub Title", + }) satisfies Options; + +describe("createWorkflows", () => { + it("creates a full set of workflows when all excludes are disabled", () => { + const workflows = createWorkflows(createOptions(false)); + + expect(workflows).toMatchInlineSnapshot(` + { + "build.yml": "jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build + - run: node ./lib/index.js + + name: Build + + on: + pull_request: ~ + push: + branches: + - main + ", + "compliance.yml": "jobs: + compliance: + runs-on: ubuntu-latest + steps: + - uses: mtfoley/pr-compliance-action@main + with: + body-auto-close: false + ignore-authors: |- + allcontributors + allcontributors[bot] + renovate + renovate[bot] + ignore-team-members: false + + name: Compliance + + on: + pull_request: + branches: + - main + types: + - edited + - opened + - reopened + - synchronize + + permissions: + pull-requests: write + ", + "contributors.yml": "jobs: + contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/prepare + - env: + GITHUB_TOKEN: \${{ secrets.ACCESS_TOKEN }} + uses: JoshuaKGoldberg/all-contributors-auto-action@v0.3.2 + + name: Contributors + + on: + push: + branches: + - main + ", + "lint-knip.yml": "jobs: + lint_knip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:knip + + name: Lint Knip + + on: + pull_request: ~ + push: + branches: + - main + ", + "lint-markdown.yml": "jobs: + lint_markdown: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:md + + name: Lint Markdown + + on: + pull_request: ~ + push: + branches: + - main + ", + "lint-package-json.yml": "jobs: + lint_package_json: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:package-json + + name: Lint Package JSON + + on: + pull_request: ~ + push: + branches: + - main + ", + "lint-packages.yml": "jobs: + lint_packages: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:packages + + name: Lint Packages + + on: + pull_request: ~ + push: + branches: + - main + ", + "lint-spelling.yml": "jobs: + lint_spelling: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint:spelling + + name: Lint spelling + + on: + pull_request: ~ + push: + branches: + - main + ", + "lint.yml": "jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint + + name: Lint + + on: + pull_request: ~ + push: + branches: + - main + ", + "post-release.yml": "jobs: + post_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: echo \\"npm_version=$(npm pkg get version | tr -d '\\"')\\" >> \\"$GITHUB_ENV\\" + - uses: apexskier/github-release-commenter@v1 + with: + GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} + comment-template: | + :tada: This is included in version {release_link} :tada: + + The release is available on: + + * [GitHub releases](https://github.com/StubOwner/stub-repository/releases/tag/{release_tag}) + * [npm package (@latest dist-tag)](https://www.npmjs.com/package/stub-repository/v/\${{ env.npm_version }}) + + Cheers! 📦🚀 + + name: Post Release + + on: + release: + types: + - published + ", + "pr-review-requested.yml": "jobs: + pr_review_requested: + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: 'status: waiting for author' + - if: failure() + run: | + echo \\"Don't worry if the previous step failed.\\" + echo \\"See https://github.com/actions-ecosystem/action-remove-labels/issues/221.\\" + + name: PR Review Requested + + on: + pull_request_target: + types: + - review_requested + + permissions: + pull-requests: write + ", + "prettier.yml": "jobs: + prettier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm format --list-different + + name: Prettier + + on: + pull_request: ~ + push: + branches: + - main + ", + "release.yml": "concurrency: + group: \${{ github.workflow }} + + jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + - uses: ./.github/actions/prepare + - run: pnpm build + - run: git config user.name \\"\${GITHUB_ACTOR}\\" + - run: git config user.email \\"\${GITHUB_ACTOR}@users.noreply.github.com\\" + - env: + NPM_TOKEN: \${{ secrets.NPM_TOKEN }} + run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN + - name: Delete branch protection on main + uses: actions/github-script@v6.4.1 + with: + github-token: \${{ secrets.ACCESS_TOKEN }} + script: | + try { + await github.request( + \`DELETE /repos/StubOwner/stub-repository/branches/main/protection\`, + ); + } catch (error) { + if (!error.message?.includes?.(\\"Branch not protected\\")) { + throw error; + } + } + - env: + GITHUB_TOKEN: \${{ secrets.ACCESS_TOKEN }} + run: | + if pnpm run should-semantic-release ; then + pnpm release-it --verbose + fi + - if: always() + name: Recreate branch protection on main + uses: actions/github-script@v6.4.1 + with: + github-token: \${{ secrets.ACCESS_TOKEN }} + script: | + github.request( + \`PUT /repos/StubOwner/stub-repository/branches/main/protection\`, + { + allow_deletions: false, + allow_force_pushes: true, + allow_fork_pushes: false, + allow_fork_syncing: true, + block_creations: false, + branch: \\"main\\", + enforce_admins: false, + owner: \\"StubOwner\\", + repo: \\"stub-repository\\", + required_conversation_resolution: true, + required_linear_history: false, + required_pull_request_reviews: null, + required_status_checks: { + checks: [ + { context: \\"build\\" }, + { context: \\"compliance\\" }, + { context: \\"lint\\" }, + { context: \\"lint_knip\\" }, + { context: \\"lint_markdown\\" }, + { context: \\"lint_package_json\\" }, + { context: \\"lint_packages\\" }, + { context: \\"lint_spelling\\" }, + { context: \\"prettier\\" }, + { context: \\"test\\" }, + ], + strict: false, + }, + restrictions: null, + } + ); + + name: Release + + on: + push: + branches: + - main + + permissions: + contents: write + id-token: write + ", + "tsc.yml": "jobs: + type_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm tsc + + name: Type Check + + on: + pull_request: ~ + push: + branches: + - main + ", + } + `); + }); + + it("creates a minimal set of workflows when all options are enabled", () => { + const workflows = createWorkflows(createOptions(true)); + + expect(workflows).toMatchInlineSnapshot(` + { + "build.yml": "jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm build + - run: node ./lib/index.js + + name: Build + + on: + pull_request: ~ + push: + branches: + - main + ", + "lint.yml": "jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm lint + + name: Lint + + on: + pull_request: ~ + push: + branches: + - main + ", + "pr-review-requested.yml": "jobs: + pr_review_requested: + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: 'status: waiting for author' + - if: failure() + run: | + echo \\"Don't worry if the previous step failed.\\" + echo \\"See https://github.com/actions-ecosystem/action-remove-labels/issues/221.\\" + + name: PR Review Requested + + on: + pull_request_target: + types: + - review_requested + + permissions: + pull-requests: write + ", + "prettier.yml": "jobs: + prettier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm format --list-different + + name: Prettier + + on: + pull_request: ~ + push: + branches: + - main + ", + "test.yml": "jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm run test --coverage + - name: Codecov + uses: codecov/codecov-action@v3 + with: + github-token: \${{ secrets.GITHUB_TOKEN }} + + name: Test + + on: + pull_request: ~ + push: + branches: + - main + ", + "tsc.yml": "jobs: + type_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - run: pnpm tsc + + name: Type Check + + on: + pull_request: ~ + push: + branches: + - main + ", + } + `); + }); +}); diff --git a/src/steps/writing/creation/dotGitHub/createWorkflows.ts b/src/steps/writing/creation/dotGitHub/createWorkflows.ts new file mode 100644 index 00000000..605c4506 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/createWorkflows.ts @@ -0,0 +1,285 @@ +/* spellchecker: disable */ +import { Options } from "../../../../shared/types.js"; +import { createWorkflowFile } from "./createWorkflowFile.js"; + +export function createWorkflows(options: Options) { + return { + "build.yml": createWorkflowFile({ + name: "Build", + runs: ["pnpm build", "node ./lib/index.js"], + }), + "pr-review-requested.yml": createWorkflowFile({ + name: "PR Review Requested", + on: { + pull_request_target: { + types: ["review_requested"], + }, + }, + permissions: { + "pull-requests": "write", + }, + steps: [ + { + uses: "actions-ecosystem/action-remove-labels@v1", + with: { + labels: "status: waiting for author", + }, + }, + { + if: "failure()", + run: 'echo "Don\'t worry if the previous step failed."\necho "See https://github.com/actions-ecosystem/action-remove-labels/issues/221."\n', + }, + ], + }), + "prettier.yml": createWorkflowFile({ + name: "Prettier", + runs: ["pnpm format --list-different"], + }), + "tsc.yml": createWorkflowFile({ + name: "Type Check", + runs: ["pnpm tsc"], + }), + ...(!options.excludeCompliance && { + "compliance.yml": createWorkflowFile({ + name: "Compliance", + on: { + pull_request: { + branches: ["main"], + types: ["edited", "opened", "reopened", "synchronize"], + }, + }, + permissions: { + "pull-requests": "write", + }, + steps: [ + { + uses: "mtfoley/pr-compliance-action@main", + with: { + "body-auto-close": false, + "ignore-authors": + [ + ...(options.excludeAllContributors + ? [] + : ["allcontributors", "allcontributors[bot]"]), + ...(options.excludeRenovate + ? [] + : ["renovate", "renovate[bot]"]), + ].join("\n") || undefined, + "ignore-team-members": false, + }, + }, + ], + }), + }), + ...(!options.excludeAllContributors && { + "contributors.yml": createWorkflowFile({ + name: "Contributors", + on: { + push: { + branches: ["main"], + }, + }, + steps: [ + { uses: "actions/checkout@v4", with: { "fetch-depth": 0 } }, + { uses: "./.github/actions/prepare" }, + { + env: { GITHUB_TOKEN: "${{ secrets.ACCESS_TOKEN }}" }, + uses: `JoshuaKGoldberg/all-contributors-auto-action@v0.3.2`, + }, + ], + }), + }), + "lint.yml": createWorkflowFile({ + name: "Lint", + runs: ["pnpm lint"], + }), + ...(!options.excludeLintKnip && { + "lint-knip.yml": createWorkflowFile({ + name: "Lint Knip", + runs: ["pnpm lint:knip"], + }), + }), + ...(!options.excludeLintMd && { + "lint-markdown.yml": createWorkflowFile({ + name: "Lint Markdown", + runs: ["pnpm lint:md"], + }), + }), + ...(!options.excludeLintPackageJson && { + "lint-package-json.yml": createWorkflowFile({ + name: "Lint Package JSON", + runs: ["pnpm lint:package-json"], + }), + }), + ...(!options.excludeLintPackages && { + "lint-packages.yml": createWorkflowFile({ + name: "Lint Packages", + runs: ["pnpm lint:packages"], + }), + }), + ...(!options.excludeLintSpelling && { + "lint-spelling.yml": createWorkflowFile({ + name: "Lint spelling", + runs: ["pnpm lint:spelling"], + }), + }), + ...(!options.excludeReleases && { + "post-release.yml": createWorkflowFile({ + name: "Post Release", + on: { + release: { + types: ["published"], + }, + }, + steps: [ + { uses: "actions/checkout@v4", with: { "fetch-depth": 0 } }, + { + run: `echo "npm_version=$(npm pkg get version | tr -d '"')" >> "$GITHUB_ENV"`, + }, + { + uses: "apexskier/github-release-commenter@v1", + with: { + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}", + "comment-template": ` + :tada: This is included in version {release_link} :tada: + + The release is available on: + + * [GitHub releases](https://github.com/${options.owner}/${options.repository}/releases/tag/{release_tag}) + * [npm package (@latest dist-tag)](https://www.npmjs.com/package/${options.repository}/v/\${{ env.npm_version }}) + + Cheers! 📦🚀 + `, + }, + }, + ], + }), + "release.yml": createWorkflowFile({ + concurrency: { + group: "${{ github.workflow }}", + }, + name: "Release", + on: { + push: { + branches: ["main"], + }, + }, + permissions: { + contents: "write", + "id-token": "write", + }, + steps: [ + { + uses: "actions/checkout@v4", + with: { + "fetch-depth": 0, + ref: "main", + }, + }, + { + uses: "./.github/actions/prepare", + }, + { + run: "pnpm build", + }, + { + run: 'git config user.name "${GITHUB_ACTOR}"', + }, + { + run: 'git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"', + }, + { + env: { + NPM_TOKEN: "${{ secrets.NPM_TOKEN }}", + }, + run: "npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN", + }, + { + name: "Delete branch protection on main", + uses: "actions/github-script@v6.4.1", + with: { + "github-token": "${{ secrets.ACCESS_TOKEN }}", + script: ` + try { + await github.request( + \`DELETE /repos/${options.owner}/${options.repository}/branches/main/protection\`, + ); + } catch (error) { + if (!error.message?.includes?.("Branch not protected")) { + throw error; + } + }`, + }, + }, + { + env: { + GITHUB_TOKEN: "${{ secrets.ACCESS_TOKEN }}", + }, + run: ` + if pnpm run should-semantic-release ; then + pnpm release-it --verbose + fi`, + }, + { + if: "always()", + name: "Recreate branch protection on main", + uses: "actions/github-script@v6.4.1", + with: { + "github-token": "${{ secrets.ACCESS_TOKEN }}", + script: ` + github.request( + \`PUT /repos/${options.owner}/${options.repository}/branches/main/protection\`, + { + allow_deletions: false, + allow_force_pushes: true, + allow_fork_pushes: false, + allow_fork_syncing: true, + block_creations: false, + branch: "main", + enforce_admins: false, + owner: "${options.owner}", + repo: "${options.repository}", + required_conversation_resolution: true, + required_linear_history: false, + required_pull_request_reviews: null, + required_status_checks: { + checks: [ + { context: "build" }, + { context: "compliance" }, + { context: "lint" }, + { context: "lint_knip" }, + { context: "lint_markdown" }, + { context: "lint_package_json" }, + { context: "lint_packages" }, + { context: "lint_spelling" }, + { context: "prettier" }, + { context: "test" }, + ], + strict: false, + }, + restrictions: null, + } + ); + `, + }, + }, + ], + }), + }), + ...(options.excludeTests && { + "test.yml": createWorkflowFile({ + name: "Test", + steps: [ + { uses: "actions/checkout@v4" }, + { uses: "./.github/actions/prepare" }, + { run: "pnpm run test --coverage" }, + { + name: "Codecov", + uses: "codecov/codecov-action@v3", + with: { "github-token": "${{ secrets.GITHUB_TOKEN }}" }, + }, + ], + }), + }), + }; +} diff --git a/src/steps/writing/creation/dotGitHub/index.ts b/src/steps/writing/creation/dotGitHub/index.ts new file mode 100644 index 00000000..876e8f63 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/index.ts @@ -0,0 +1,14 @@ +import { Options } from "../../../../shared/types.js"; +import { createDotGitHubActions } from "./actions.js"; +import { createDotGitHubFiles } from "./createDotGitHubFiles.js"; +import { createWorkflows } from "./createWorkflows.js"; +import { createDotGitHubIssueTemplate } from "./issueTemplate.js"; + +export async function createDotGitHub(options: Options) { + return { + ISSUE_TEMPLATE: createDotGitHubIssueTemplate(options), + actions: createDotGitHubActions(), + workflows: createWorkflows(options), + ...(await createDotGitHubFiles(options)), + }; +} diff --git a/src/steps/writing/creation/dotGitHub/issueTemplate.ts b/src/steps/writing/creation/dotGitHub/issueTemplate.ts new file mode 100644 index 00000000..a034aec8 --- /dev/null +++ b/src/steps/writing/creation/dotGitHub/issueTemplate.ts @@ -0,0 +1,210 @@ +import { Options } from "../../../../shared/types.js"; +import { formatYaml } from "../formatters/formatYaml.js"; + +export function createDotGitHubIssueTemplate({ + owner, + repository, +}: Pick) { + return { + "01-bug.yml": formatYaml({ + body: [ + { + attributes: { + description: + "If any of these required steps are not taken, we may not be able to review your issue. Help us to help you!", + label: "Bug Report Checklist", + options: [ + { + label: "I have tried restarting my IDE and the issue persists.", + required: true, + }, + { + label: + "I have pulled the latest `main` branch of the repository.", + required: true, + }, + { + label: `I have [searched for related issues](https://github.com/${owner}/${repository}/issues?q=is%3Aissue) and found none that matched my issue.`, + required: true, + }, + ], + }, + type: "checkboxes", + }, + { + attributes: { + description: "What did you expect to happen?", + label: "Expected", + }, + type: "textarea", + validations: { + required: true, + }, + }, + { + attributes: { + description: "What happened instead?", + label: "Actual", + }, + type: "textarea", + validations: { + required: true, + }, + }, + { + attributes: { + description: "Any additional info you'd like to provide.", + label: "Additional Info", + }, + type: "textarea", + }, + ], + description: "Report a bug trying to run the code", + labels: ["type: bug"], + name: "🐛 Bug", + title: "🐛 Bug: ", + }), + "02-documentation.yml": formatYaml({ + body: [ + { + attributes: { + description: + "If any of these required steps are not taken, we may not be able to review your issue. Help us to help you!", + label: "Bug Report Checklist", + options: [ + { + label: + "I have pulled the latest `main` branch of the repository.", + required: true, + }, + { + label: `I have [searched for related issues](https://github.com/${owner}/${repository}/issues?q=is%3Aissue) and found none that matched my issue.`, + required: true, + }, + ], + }, + type: "checkboxes", + }, + { + attributes: { + description: "What would you like to report?", + label: "Overview", + }, + type: "textarea", + validations: { + required: true, + }, + }, + { + attributes: { + description: "Any additional info you'd like to provide.", + label: "Additional Info", + }, + type: "textarea", + }, + ], + description: "Report a typo or missing area of documentation", + labels: ["area: documentation"], + name: "📝 Documentation", + title: "📝 Documentation: ", + }), + "03-feature.yml": formatYaml({ + body: [ + { + attributes: { + description: + "If any of these required steps are not taken, we may not be able to review your issue. Help us to help you!", + label: "Bug Report Checklist", + options: [ + { + label: "I have tried restarting my IDE and the issue persists.", + required: true, + }, + { + label: + "I have pulled the latest `main` branch of the repository.", + required: true, + }, + { + label: `I have [searched for related issues](https://github.com/${owner}/${repository}/issues?q=is%3Aissue) and found none that matched my issue.`, + required: true, + }, + ], + }, + type: "checkboxes", + }, + { + attributes: { + description: "What did you expect to be able to do?", + label: "Overview", + }, + type: "textarea", + validations: { + required: true, + }, + }, + { + attributes: { + description: "Any additional info you'd like to provide.", + label: "Additional Info", + }, + type: "textarea", + }, + ], + description: + "Request that a new feature be added or an existing feature improved", + labels: ["type: feature"], + name: "🚀 Feature", + title: "🚀 Feature: ", + }), + "04-tooling.yml": formatYaml({ + body: [ + { + attributes: { + description: + "If any of these required steps are not taken, we may not be able to review your issue. Help us to help you!", + label: "Bug Report Checklist", + options: [ + { + label: "I have tried restarting my IDE and the issue persists.", + required: true, + }, + { + label: + "I have pulled the latest `main` branch of the repository.", + required: true, + }, + { + label: `I have [searched for related issues](https://github.com/${owner}/${repository}/issues?q=is%3Aissue) and found none that matched my issue.`, + required: true, + }, + ], + }, + type: "checkboxes", + }, + { + attributes: { + description: "What did you expect to be able to do?", + label: "Overview", + }, + type: "textarea", + validations: { + required: true, + }, + }, + { + attributes: { + description: "Any additional info you'd like to provide.", + label: "Additional Info", + }, + type: "textarea", + }, + ], + description: + "Report a bug or request an enhancement in repository tooling", + labels: ["area: tooling"], + name: "🛠 Tooling", + title: "🛠 Tooling: ", + }), + }; +} diff --git a/src/steps/writing/creation/dotHusky.ts b/src/steps/writing/creation/dotHusky.ts new file mode 100644 index 00000000..b3484d7e --- /dev/null +++ b/src/steps/writing/creation/dotHusky.ts @@ -0,0 +1,12 @@ +import { formatIgnoreFile } from "./formatters/formatIgnoreFile.js"; + +export function createDotHusky() { + return { + ".gitignore": formatIgnoreFile(["_"]), + "pre-commit": formatIgnoreFile([ + `#!/bin/sh`, + `. "$(dirname "$0")/_/husky.sh"`, + "npx lint-staged", + ]), + }; +} diff --git a/src/steps/writing/creation/dotVSCode.ts b/src/steps/writing/creation/dotVSCode.ts new file mode 100644 index 00000000..f63eb893 --- /dev/null +++ b/src/steps/writing/creation/dotVSCode.ts @@ -0,0 +1,54 @@ +import { Options } from "../../../shared/types.js"; +import { formatJson } from "./formatters/formatJson.js"; + +/* spellchecker: disable */ +export async function createDotVSCode(options: Options) { + return { + "extensions.json": await formatJson({ + recommendations: [ + "DavidAnson.vscode-markdownlint", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + !options.excludeLintSpelling && "streetsidesoftware.code-spell-checker", + ].filter(Boolean), + }), + ...(!options.excludeTests && { + "launch.json": await formatJson({ + configurations: [ + { + args: ["run", "${relativeFile}"], + autoAttachChildProcesses: true, + console: "integratedTerminal", + name: "Debug Current Test File", + program: "${workspaceRoot}/node_modules/vitest/vitest.mjs", + request: "launch", + skipFiles: ["/**", "**/node_modules/**"], + smartStep: true, + type: "node", + }, + ], + version: "0.2.0", + }), + }), + "settings.json": await formatJson({ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.rulers": [80], + "eslint.probe": [ + "javascript", + "javascriptreact", + "json", + "jsonc", + "markdown", + "typescript", + "typescriptreact", + "yaml", + ], + "eslint.rules.customizations": [{ rule: "*", severity: "warn" }], + "typescript.tsdk": "node_modules/typescript/lib", + }), + }; +} diff --git a/src/steps/writing/creation/formatters/formatIgnoreFile.ts b/src/steps/writing/creation/formatters/formatIgnoreFile.ts new file mode 100644 index 00000000..f4a18355 --- /dev/null +++ b/src/steps/writing/creation/formatters/formatIgnoreFile.ts @@ -0,0 +1,3 @@ +export function formatIgnoreFile(lines: string[]) { + return [...lines, ""].join("\n"); +} diff --git a/src/steps/writing/creation/formatters/formatJson.test.ts b/src/steps/writing/creation/formatters/formatJson.test.ts new file mode 100644 index 00000000..0b01d8a5 --- /dev/null +++ b/src/steps/writing/creation/formatters/formatJson.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; + +import { formatJson } from "./formatJson.js"; + +describe("formatJson", () => { + it("removes undefined values", async () => { + const actual = await formatJson({ empty: undefined, exists: true }); + + expect(actual).toMatchInlineSnapshot(` + "{ \\"exists\\": true } + " + `); + }); +}); diff --git a/src/steps/writing/creation/formatters/formatJson.ts b/src/steps/writing/creation/formatters/formatJson.ts new file mode 100644 index 00000000..d80b66ce --- /dev/null +++ b/src/steps/writing/creation/formatters/formatJson.ts @@ -0,0 +1,14 @@ +import prettier from "prettier"; + +export async function formatJson(value: object) { + return await prettier.format( + JSON.stringify( + Object.fromEntries( + Object.entries(value).filter((entry) => entry[1] !== undefined), + ), + ), + { + parser: "json", + }, + ); +} diff --git a/src/steps/writing/creation/formatters/formatTypeScript.ts b/src/steps/writing/creation/formatters/formatTypeScript.ts new file mode 100644 index 00000000..e18daea6 --- /dev/null +++ b/src/steps/writing/creation/formatters/formatTypeScript.ts @@ -0,0 +1,5 @@ +import prettier from "prettier"; + +export async function formatTypeScript(value: string) { + return await prettier.format(value, { parser: "typescript" }); +} diff --git a/src/steps/writing/creation/formatters/formatYaml.ts b/src/steps/writing/creation/formatters/formatYaml.ts new file mode 100644 index 00000000..cbfd753c --- /dev/null +++ b/src/steps/writing/creation/formatters/formatYaml.ts @@ -0,0 +1,26 @@ +import jsYaml from "js-yaml"; + +const options: jsYaml.DumpOptions = { + lineWidth: -1, + noCompatMode: true, + // https://github.com/nodeca/js-yaml/pull/515 + replacer(_, value: unknown) { + if (typeof value !== "string" || !value.includes("\n\t\t")) { + return value; + } + + return value + .replaceAll(": |-\n", ": |\n") + .replaceAll("\n\t \t\t\t", "") + + .replaceAll(/\n\t\t\t\t\t\t$/g, ""); + }, + sortKeys: true, + styles: { + "!!null": "canonical", + }, +}; + +export function formatYaml(value: unknown) { + return jsYaml.dump(value, options).replaceAll(`\\"`, `"`); +} diff --git a/src/steps/writing/creation/index.ts b/src/steps/writing/creation/index.ts new file mode 100644 index 00000000..897e6b35 --- /dev/null +++ b/src/steps/writing/creation/index.ts @@ -0,0 +1,17 @@ +import { Options } from "../../../shared/types.js"; +import { Structure } from "../types.js"; +import { createDotGitHub } from "./dotGitHub/index.js"; +import { createDotHusky } from "./dotHusky.js"; +import { createDotVSCode } from "./dotVSCode.js"; +import { createRootFiles } from "./rootFiles.js"; +import { createSrc } from "./src.js"; + +export async function createStructure(options: Options): Promise { + return { + ".github": await createDotGitHub(options), + ".husky": createDotHusky(), + ".vscode": await createDotVSCode(options), + ...(options.mode !== "migrate" && { src: await createSrc(options) }), + ...(await createRootFiles(options)), + }; +} diff --git a/src/steps/writing/creation/rootFiles.ts b/src/steps/writing/creation/rootFiles.ts new file mode 100644 index 00000000..427755f4 --- /dev/null +++ b/src/steps/writing/creation/rootFiles.ts @@ -0,0 +1,208 @@ +import { Options } from "../../../shared/types.js"; +import { createESLintRC } from "./createESLintRC.js"; +import { formatIgnoreFile } from "./formatters/formatIgnoreFile.js"; +import { formatJson } from "./formatters/formatJson.js"; +import { formatTypeScript } from "./formatters/formatTypeScript.js"; +import { writeAllContributorsRC } from "./writeAllContributorsRC.js"; +import { writePackageJson } from "./writePackageJson.js"; + +export async function createRootFiles(options: Options) { + return { + ".all-contributorsrc": await writeAllContributorsRC(options), + ".eslintignore": formatIgnoreFile( + [ + "!.*", + ...(options.excludeTests ? [] : ["coverage"]), + "lib", + "node_modules", + "pnpm-lock.yaml", + ].filter(Boolean), + ), + ".eslintrc.cjs": await createESLintRC(options), + ".gitignore": formatIgnoreFile([ + ...(options.excludeTests ? [] : ["coverage/"]), + "lib/", + "node_modules/", + ]), + ...(!options.excludeLintMd && { + ".markdownlint.json": await formatJson({ + extends: "markdownlint/style/prettier", + "first-line-h1": false, + "no-inline-html": false, + }), + ".markdownlintignore": formatIgnoreFile([ + ".github/CODE_OF_CONDUCT.md", + "CHANGELOG.md", + "lib/", + "node_modules/", + ]), + }), + ...(!options.excludeLintPackageJson && { + ".npmpackagejsonlintrc.json": await formatJson({ + extends: "npm-package-json-lint-config-default", + rules: { + "require-description": "error", + "require-license": "error", + }, + }), + }), + ".nvmrc": `18.18.0\n`, + ".prettierignore": formatIgnoreFile([ + ...(options.excludeAllContributors ? [] : [".all-contributorsrc"]), + ...(options.excludeTests ? [] : ["coverage/"]), + "lib/", + "pnpm-lock.yaml", + ]), + ".prettierrc": await formatJson({ + $schema: "http://json.schemastore.org/prettierrc", + overrides: [ + { + files: ".*rc", + options: { parser: "json" }, + }, + { + files: ".nvmrc", + options: { parser: "yaml" }, + }, + ], + plugins: ["prettier-plugin-curly", "prettier-plugin-packagejson"], + useTabs: true, + }), + ...(!options.excludeReleases && { + ".release-it.json": await formatJson({ + git: { + commitMessage: "chore: release v${version}", + requireCommits: true, + }, + github: { + autoGenerate: true, + release: true, + releaseName: "v${version}", + }, + npm: { + publishArgs: [`--access ${options.access}`, "--provenance"], + }, + plugins: { + "@release-it/conventional-changelog": { + infile: "CHANGELOG.md", + preset: "angular", + }, + }, + }), + }), + "LICENSE.md": `# MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +`, + ...(!options.excludeLintSpelling && { + "cspell.json": await formatJson({ + dictionaries: ["typescript"], + ignorePaths: [ + ".github", + "CHANGELOG.md", + ...(options.excludeTests ? [] : ["coverage"]), + "lib", + "node_modules", + "pnpm-lock.yaml", + ], + words: [ + "Codecov", + "codespace", + "commitlint", + "contributorsrc", + "conventionalcommits", + ...(options.excludeLintKnip ? [] : ["knip"]), + "lcov", + "markdownlintignore", + "npmpackagejsonlintrc", + "outro", + "packagejson", + "tsup", + "quickstart", + "wontfix", + ].sort(), + }), + }), + ...(!options.excludeLintKnip && { + "knip.jsonc": await formatJson({ + $schema: "https://unpkg.com/knip@latest/schema.json", + entry: ["src/index.ts!"], + ignoreExportsUsedInFile: { + interface: true, + type: true, + }, + project: ["src/**/*.ts!"], + }), + }), + "package.json": await writePackageJson(options), + "tsconfig.eslint.json": await formatJson({ + extends: "./tsconfig.json", + include: ["."], + }), + "tsconfig.json": await formatJson({ + compilerOptions: { + declaration: true, + declarationMap: true, + esModuleInterop: true, + module: "NodeNext", + moduleResolution: "NodeNext", + noEmit: true, + outDir: "lib", + resolveJsonModule: true, + skipLibCheck: true, + sourceMap: true, + strict: true, + target: "ES2022", + }, + include: ["src"], + }), + "tsup.config.ts": + await formatTypeScript(`import { defineConfig } from "tsup"; + + export default defineConfig({ + bundle: false, + clean: true, + dts: true, + entry: ["src/**/*.ts"${options.excludeTests ? "" : `, "!src/**/*.test.*"`}], + format: "esm", + outDir: "lib", + sourcemap: true, + }); + `), + ...(!options.excludeTests && { + "vitest.config.ts": `import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + clearMocks: true, + coverage: { + all: true, + exclude: ["lib"], + include: ["src"], + reporter: ["html", "lcov"], + }, + exclude: ["lib", "node_modules"], + setupFiles: ["console-fail-test/setup"], + }, +}); +`, + }), + }; +} diff --git a/src/steps/writing/creation/src.ts b/src/steps/writing/creation/src.ts new file mode 100644 index 00000000..895d44df --- /dev/null +++ b/src/steps/writing/creation/src.ts @@ -0,0 +1,88 @@ +import { Options } from "../../../shared/types.js"; +import { formatTypeScript } from "./formatters/formatTypeScript.js"; + +export async function createSrc(options: Options) { + return { + ...(!options.excludeTests && { + "greet.test.ts": await formatTypeScript( + ` + import { describe, expect, it, vi } from "vitest"; + + import { greet } from "./greet.js"; + + const message = "Yay, testing!"; + + describe("greet", () => { + it("logs to the console once when message is provided as a string", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet(message); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs to the console once when message is provided as an object", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet({ message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs once when times is not provided in an object", () => { + const logger = vi.fn(); + + greet({ logger, message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs a specified number of times when times is provided", () => { + const logger = vi.fn(); + const times = 7; + + greet({ logger, message, times }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(7); + }); + }); + `, + ), + }), + "greet.ts": await formatTypeScript( + `import { GreetOptions } from "./types.js"; + + export function greet(options: GreetOptions | string) { + const { + logger = console.log.bind(console), + message, + times = 1, + } = typeof options === "string" ? { message: options } : options; + + for (let i = 0; i < times; i += 1) { + logger(message); + } + } + `, + ), + "index.ts": await formatTypeScript( + ` + export * from "./greet.js"; + export * from "./types.js"; + `, + ), + "types.ts": await formatTypeScript( + ` + export interface GreetOptions { + logger?: (message: string) => void; + message: string; + times?: number; + } + `, + ), + }; +} diff --git a/src/steps/writing/creation/writeAllContributorsRC.ts b/src/steps/writing/creation/writeAllContributorsRC.ts new file mode 100644 index 00000000..72ef07a5 --- /dev/null +++ b/src/steps/writing/creation/writeAllContributorsRC.ts @@ -0,0 +1,25 @@ +import { readFileSafeAsJson } from "../../../shared/readFileSafeAsJson.js"; +import { AllContributorsData, Options } from "../../../shared/types.js"; +import { formatJson } from "./formatters/formatJson.js"; + +export async function writeAllContributorsRC(options: Options) { + const existing = (await readFileSafeAsJson( + ".all-contributorsrc", + )) as AllContributorsData | null; + + return await formatJson({ + badgeTemplate: + 'All Contributors: <%= contributors.length %>', + commit: false, + commitConvention: "angular", + contributors: existing?.contributors ?? [], + contributorsPerLine: 7, + contributorsSortAlphabetically: true, + files: ["README.md"], + imageSize: 100, + projectName: options.repository, + projectOwner: options.owner, + repoHost: "https://github.com", + repoType: "github", + }); +} diff --git a/src/steps/writing/creation/writePackageJson.test.ts b/src/steps/writing/creation/writePackageJson.test.ts new file mode 100644 index 00000000..6c9ae78b --- /dev/null +++ b/src/steps/writing/creation/writePackageJson.test.ts @@ -0,0 +1,204 @@ +import { describe, expect, it, vi } from "vitest"; + +import { Options } from "../../../shared/types.js"; +import { writePackageJson } from "./writePackageJson.js"; + +const mockReadFileSafeAsJson = vi.fn(); + +vi.mock("../../../shared/readFileSafeAsJson.js", () => ({ + get readFileSafeAsJson() { + return mockReadFileSafeAsJson; + }, +})); + +const options = { + access: "public", + author: "test-author", + base: "everything", + createRepository: undefined, + description: "test-description", + email: { + github: "github@email.com", + npm: "npm@email.com", + }, + excludeAllContributors: undefined, + excludeCompliance: undefined, + excludeLintJson: undefined, + excludeLintKnip: undefined, + excludeLintMd: undefined, + excludeLintPackageJson: undefined, + excludeLintPackages: undefined, + excludeLintPerfectionist: undefined, + excludeLintSpelling: undefined, + excludeLintYml: undefined, + excludeReleases: false, + excludeRenovate: undefined, + excludeTests: false, + funding: undefined, + logo: undefined, + mode: "create", + owner: "test-owner", + repository: "test-repository", + skipGitHubApi: false, + skipInstall: undefined, + skipRemoval: undefined, + skipRestore: undefined, + skipUninstall: undefined, + title: "", +} satisfies Options; + +describe("writePackageJson", () => { + it("preserves existing dependencies when they exist", async () => { + const dependencies = { abc: "1.2.3" }; + mockReadFileSafeAsJson.mockResolvedValue({ dependencies }); + + const packageJson = await writePackageJson(options); + + expect(JSON.parse(packageJson)).toEqual( + expect.objectContaining({ dependencies }), + ); + }); + + it("preserves existing devDependencies that aren't known to be unnecessary when they exist", async () => { + const devDependencies = { abc: "1.2.3", jest: "4.5.6" }; + mockReadFileSafeAsJson.mockResolvedValue({ devDependencies }); + + const packageJson = await writePackageJson(options); + + expect(JSON.parse(packageJson)).toEqual( + expect.objectContaining({ devDependencies }), + ); + }); + + it("includes flattened keywords when they're specified", async () => { + mockReadFileSafeAsJson.mockResolvedValue({}); + + const keywords = ["abc", "def ghi", "jkl mno pqr"]; + const packageJson = await writePackageJson({ ...options, keywords }); + + expect(JSON.parse(packageJson)).toEqual( + expect.objectContaining({ + keywords: ["abc", "def", "ghi", "jkl", "mno", "pqr"], + }), + ); + }); + + it("includes all optional portions when no exclusions are enabled", async () => { + mockReadFileSafeAsJson.mockResolvedValue({}); + + const packageJson = await writePackageJson(options); + + expect(JSON.parse(packageJson)).toMatchInlineSnapshot(` + { + "author": { + "email": "npm@email.com", + "name": "test-author", + }, + "description": "test-description", + "devDependencies": {}, + "engines": { + "node": ">=18", + }, + "files": [ + "lib/", + "package.json", + "LICENSE.md", + "README.md", + ], + "license": "MIT", + "lint-staged": { + "*": "prettier --ignore-unknown --write", + }, + "main": "./lib/index.js", + "name": "test-repository", + "packageManager": "pnpm@8.7.0", + "publishConfig": { + "provenance": true, + }, + "repository": { + "type": "git", + "url": "https://github.com/test-owner/test-repository", + }, + "scripts": { + "build": "tsup", + "format": "prettier \\"**/*\\" --ignore-unknown", + "lint": "eslint . .*js --max-warnings 0 --report-unused-disable-directives", + "lint:knip": "knip", + "lint:md": "markdownlint \\"**/*.md\\" \\".github/**/*.md\\" --rules sentences-per-line", + "lint:package-json": "npmPkgJsonLint .", + "lint:packages": "pnpm dedupe --check", + "lint:spelling": "cspell \\"**\\" \\".github/**/*\\"", + "prepare": "husky install", + "should-semantic-release": "should-semantic-release --verbose", + "test": "vitest", + "tsc": "tsc", + }, + "type": "module", + "version": "0.0.0", + } + `); + }); + + it("excludes all optional portions when all exclusions are enabled", async () => { + mockReadFileSafeAsJson.mockResolvedValue({}); + + const packageJson = await writePackageJson({ + ...options, + excludeAllContributors: true, + excludeCompliance: true, + excludeLintJson: true, + excludeLintKnip: true, + excludeLintMd: true, + excludeLintPackageJson: true, + excludeLintPackages: true, + excludeLintPerfectionist: true, + excludeLintSpelling: true, + excludeLintYml: true, + excludeReleases: true, + excludeRenovate: true, + }); + + expect(JSON.parse(packageJson)).toMatchInlineSnapshot(` + { + "author": { + "email": "npm@email.com", + "name": "test-author", + }, + "description": "test-description", + "devDependencies": {}, + "engines": { + "node": ">=18", + }, + "files": [ + "lib/", + "package.json", + "LICENSE.md", + "README.md", + ], + "license": "MIT", + "lint-staged": { + "*": "prettier --ignore-unknown --write", + }, + "main": "./lib/index.js", + "name": "test-repository", + "packageManager": "pnpm@8.7.0", + "publishConfig": { + "provenance": true, + }, + "repository": { + "type": "git", + "url": "https://github.com/test-owner/test-repository", + }, + "scripts": { + "build": "tsup", + "format": "prettier \\"**/*\\" --ignore-unknown", + "lint": "eslint . .*js --max-warnings 0 --report-unused-disable-directives", + "prepare": "husky install", + "tsc": "tsc", + }, + "type": "module", + "version": "0.0.0", + } + `); + }); +}); diff --git a/src/steps/writing/creation/writePackageJson.ts b/src/steps/writing/creation/writePackageJson.ts new file mode 100644 index 00000000..69fe13f2 --- /dev/null +++ b/src/steps/writing/creation/writePackageJson.ts @@ -0,0 +1,118 @@ +import { readFileSafeAsJson } from "../../../shared/readFileSafeAsJson.js"; +import { Options } from "../../../shared/types.js"; +import { formatJson } from "./formatters/formatJson.js"; + +const devDependenciesToRemove = [ + "@babel/core", + "@babel/preset-env", + "@babel/preset-react", + "@babel/preset-typescript", + "@swc/jest", + "@vitest/coverage-istanbul", + "ava", + "babel-jest", + "commitlint", + "cson-parser", + "esbuild", + "eslint-plugin-jest", + "eslint-plugin-prettier", + "eslint-plugin-simple-import-sort", + "eslint-plugin-typescript-sort-keys", + "jasmine", + "jest", + "mocha", + "npm-run-all", + "pnpm-deduplicate", + "pretty-quick", + "ts-jest", +]; + +export async function writePackageJson(options: Options) { + const existingPackageJson = + ((await readFileSafeAsJson("./package.json")) as null | object) ?? {}; + + return await formatJson({ + // If we didn't already have a version, set it to 0.0.0 + version: "0.0.0", + + // To start, copy over all existing package fields (e.g. "dependencies") + ...existingPackageJson, + + author: { email: options.email.npm, name: options.author }, + description: options.description, + keywords: options.keywords?.length + ? options.keywords.flatMap((keyword) => keyword.split(/ /)) + : undefined, + + // We copy all existing dev dependencies except those we know are not used anymore + devDependencies: copyDevDependencies(existingPackageJson), + + // Remove fields we know we don't want, such as old or redundant configs + eslintConfig: undefined, + husky: undefined, + prettierConfig: undefined, + types: undefined, + + // The rest of the fields are ones we know from our template + engines: { + node: ">=18", + }, + files: ["lib/", "package.json", "LICENSE.md", "README.md"], + license: "MIT", + "lint-staged": { + "*": "prettier --ignore-unknown --write", + }, + main: "./lib/index.js", + name: options.repository, + packageManager: "pnpm@8.7.0", + publishConfig: { + provenance: true, + }, + repository: { + type: "git", + url: `https://github.com/${options.owner}/${options.repository}`, + }, + scripts: { + build: "tsup", + format: 'prettier "**/*" --ignore-unknown', + lint: "eslint . .*js --max-warnings 0 --report-unused-disable-directives", + ...(!options.excludeLintKnip && { + "lint:knip": "knip", + }), + ...(!options.excludeLintMd && { + "lint:md": + 'markdownlint "**/*.md" ".github/**/*.md" --rules sentences-per-line', + }), + ...(!options.excludeLintPackageJson && { + "lint:package-json": "npmPkgJsonLint .", + }), + ...(!options.excludeLintPackages && { + "lint:packages": "pnpm dedupe --check", + }), + ...(!options.excludeLintSpelling && { + "lint:spelling": 'cspell "**" ".github/**/*"', + }), + prepare: "husky install", + ...(!options.excludeReleases && { + "should-semantic-release": "should-semantic-release --verbose", + }), + ...(!options.excludeReleases && { test: "vitest" }), + tsc: "tsc", + }, + type: "module", + }); +} + +function copyDevDependencies(existingPackageJson: object) { + const devDependencies = + "devDependencies" in existingPackageJson + ? (existingPackageJson.devDependencies as Record) + : {}; + + for (const devDependencyToRemove of devDependenciesToRemove) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete devDependencies[devDependencyToRemove]; + } + + return devDependencies; +} diff --git a/src/steps/writing/types.ts b/src/steps/writing/types.ts new file mode 100644 index 00000000..6b5449c3 --- /dev/null +++ b/src/steps/writing/types.ts @@ -0,0 +1,3 @@ +export interface Structure { + [i: string]: Structure | string; +} diff --git a/src/steps/writing/writeStructure.ts b/src/steps/writing/writeStructure.ts new file mode 100644 index 00000000..6ac11046 --- /dev/null +++ b/src/steps/writing/writeStructure.ts @@ -0,0 +1,12 @@ +import { $ } from "execa"; + +import { Options } from "../../shared/types.js"; +import { createStructure } from "./creation/index.js"; +import { writeStructureWorker } from "./writeStructureWorker.js"; + +export async function writeStructure(options: Options) { + await writeStructureWorker(await createStructure(options), "."); + + // https://github.com/JoshuaKGoldberg/create-typescript-app/issues/718 + await $`chmod ug+x .husky/pre-commit`; +} diff --git a/src/steps/writing/writeStructureWorker.test.ts b/src/steps/writing/writeStructureWorker.test.ts new file mode 100644 index 00000000..92c03b1c --- /dev/null +++ b/src/steps/writing/writeStructureWorker.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from "vitest"; + +import { writeStructureWorker } from "./writeStructureWorker.js"; + +const mockMkdir = vi.fn(); +const mockWriteFile = vi.fn(); + +vi.mock("node:fs/promises", () => ({ + get mkdir() { + return mockMkdir; + }, + get writeFile() { + return mockWriteFile; + }, +})); + +describe("writeStructureWorker", () => { + it("writes an unformatted file when structure has a file", async () => { + await writeStructureWorker( + { + file: "content", + }, + ".", + ); + + expect(mockMkdir).toHaveBeenCalledWith(".", { recursive: true }); + expect(mockWriteFile).toHaveBeenCalledWith("file", "content"); + }); + + it.each([ + ["implicit json", ".rc", '{ "value": true }', '{ "value": true }\n'], + ["cjs", "file.cjs", " module.exports = { };", "module.exports = {};\n"], + ["js", "file.js", " export default { }", "export default {};\n"], + ["explicit json", "file.json", "{ }", "{}\n"], + ["md", "file.md", " # h1 ", "# h1\n"], + ["yml", "file.yml", " on: true ", "on: true\n"], + ])("writes a formatted %s file", async (_, file, input, output) => { + await writeStructureWorker({ [file]: input }, "."); + expect(mockWriteFile).toHaveBeenCalledWith(file, output); + }); + + it("writes a nested file when structure has a file inside a directory", async () => { + await writeStructureWorker( + { + directory: { + file: "content", + }, + }, + ".", + ); + + expect(mockMkdir).toHaveBeenCalledWith(".", { recursive: true }); + expect(mockMkdir).toHaveBeenCalledWith("directory", { recursive: true }); + }); +}); diff --git a/src/steps/writing/writeStructureWorker.ts b/src/steps/writing/writeStructureWorker.ts new file mode 100644 index 00000000..9a02547d --- /dev/null +++ b/src/steps/writing/writeStructureWorker.ts @@ -0,0 +1,55 @@ +import * as fs from "node:fs/promises"; +import * as path from "path"; +import prettier from "prettier"; + +import { Structure } from "./types.js"; + +export async function writeStructureWorker( + structure: Structure, + basePath: string, +) { + await fs.mkdir(basePath, { recursive: true }); + + for (const [fileName, contents] of Object.entries(structure)) { + if (typeof contents === "string") { + await fs.writeFile( + path.join(basePath, fileName), + await format(fileName, contents), + ); + } else { + await writeStructureWorker(contents, path.join(basePath, fileName)); + } + } +} + +async function format(fileName: string, text: string) { + const parser = inferParser(fileName, text); + if (!parser) { + return text; + } + + return await prettier.format(text, { + parser, + useTabs: true, + }); +} + +function inferParser(fileName: string, text: string) { + if (text.startsWith("{")) { + return "json"; + } + + switch (fileName.split(".").at(-1)) { + case "cjs": + case "js": + return "babel"; + case "json": + return "json"; + case "md": + return "markdown"; + case "yml": + return "yaml"; + } + + return undefined; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..4f16ae39 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,5 @@ +export interface GreetOptions { + logger?: (message: string) => void; + message: string; + times?: number; +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 00000000..3e219c8f --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1 @@ +{ "extends": "./tsconfig.json", "include": ["."] } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..f0eaf1a8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "outDir": "lib", + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2022" + }, + "include": ["src"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 00000000..43c6f24e --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + bundle: false, + clean: true, + dts: true, + entry: ["src/**/*.ts", "!src/**/*.test.*"], + format: "esm", + outDir: "lib", + sourcemap: true, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..36fbb032 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + clearMocks: true, + coverage: { + all: true, + exclude: ["lib"], + include: ["src"], + reporter: ["html", "lcov"], + }, + exclude: ["lib", "node_modules"], + setupFiles: ["console-fail-test/setup"], + }, +});