diff --git a/README.md b/README.md index 6964ab31..9c9e9289 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Check the README for each package within the `packages` directory for specific u | Package | Version | Description | |---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| | [multi-semantic-release](packages/multi-semantic-release/README.md) | ![npm](https://img.shields.io/npm/v/@anolilab/multi-semantic-release?style=flat-square&labelColor=292a44&color=663399&label=v) | A hacky semantic-release for monorepos based on qiwi/multi-semantic-release | | | +| [semantic-release-pnpm](packages/semantic-release-pnpm/README.md) | ![npm](https://img.shields.io/npm/v/@anolilab/semantic-release-pnpm?style=flat-square&labelColor=292a44&color=663399&label=v) | Semantic-release plugin to publish a npm package with pnpm | | | ## How We Version diff --git a/packages/rc/.eslintignore b/packages/rc/.eslintignore new file mode 100644 index 00000000..e3f5dd77 --- /dev/null +++ b/packages/rc/.eslintignore @@ -0,0 +1,15 @@ +dist +node_modules +coverage + +__fixtures__ +__docs__ + +vitest.config.ts +.prettierrc.cjs +tsup.config.ts +.secretlintrc.cjs +tsconfig.eslint.json + +README.md + diff --git a/packages/rc/.eslintrc.cjs b/packages/rc/.eslintrc.cjs new file mode 100644 index 00000000..9c2fe8fe --- /dev/null +++ b/packages/rc/.eslintrc.cjs @@ -0,0 +1,90 @@ +/** @ts-check */ +// eslint-disable-next-line import/no-commonjs,import/no-unused-modules +const { defineConfig } = require("@anolilab/eslint-config/define-config"); +// eslint-disable-next-line import/no-commonjs +const globals = require("@anolilab/eslint-config/globals"); + +/// +/// +/// +/// +/// + +/** @type {import('eslint').Linter.Config} */ +module.exports = defineConfig({ + env: { + // Your environments (which contains several predefined global variables) + // Most environments are loaded automatically if our rules are added + }, + extends: ["@anolilab/eslint-config", "@anolilab/eslint-config/typescript-type-checking"], + globals: { + ...globals.es2021, + // Your global variables (setting to false means it's not allowed to be reassigned) + // myGlobal: false + }, + ignorePatterns: ["!**/*"], + overrides: [ + { + files: ["*.ts", "*.tsx", "*.mts", "*.cts", "*.js", "*.jsx"], + // Set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting + parserOptions: {}, + rules: {}, + }, + { + files: ["*.ts", "*.tsx", "*.mts", "*.cts"], + // Set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting + parserOptions: {}, + rules: { + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "prefer-template": "off", + }, + }, + { + files: ["*.js", "*.jsx"], + rules: {}, + }, + { + files: ["*.mdx"], + rules: { + "jsx-a11y/anchor-has-content": "off", + // @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/917 + "jsx-a11y/heading-has-content": "off", + }, + }, + { + files: ["src/index.ts"], + rules: { + "import/no-unused-modules": "off", + }, + }, + { + files: ["__docs__/**"], + rules: { + "import/no-unresolved": "off", + "import/no-unused-modules": "off", + "no-console": "off", + "no-undef": "off", + "no-unused-vars": "off", + "unicorn/prefer-top-level-await": "off", + }, + }, + { + files: ["__tests__/**"], + rules: { + "import/no-unused-modules": "off", + }, + }, + ], + parserOptions: { + ecmaVersion: 2021, + project: "./tsconfig.eslint.json", + sourceType: "module", + }, + // Report unused `eslint-disable` comments. + reportUnusedDisableDirectives: true, + root: true, +}); diff --git a/packages/rc/.npmignore b/packages/rc/.npmignore new file mode 100644 index 00000000..ff4abfc4 --- /dev/null +++ b/packages/rc/.npmignore @@ -0,0 +1,9 @@ +package-lock.json + +src +__tests__ +__stories__ +__fixtures__ +.rpt2_cache +fixup.sh +.releaserc.json diff --git a/packages/rc/.prettierignore b/packages/rc/.prettierignore new file mode 100644 index 00000000..ebf52909 --- /dev/null +++ b/packages/rc/.prettierignore @@ -0,0 +1,9 @@ +.gitkeep +.env* +*.ico +*.lock +dist +CHANGELOG.md +coverage +node_modules +.eslintcache diff --git a/packages/rc/.prettierrc.cjs b/packages/rc/.prettierrc.cjs new file mode 100644 index 00000000..32f893c0 --- /dev/null +++ b/packages/rc/.prettierrc.cjs @@ -0,0 +1,5 @@ +const config = require("@anolilab/prettier-config"); + +module.exports = { + ...config, +}; diff --git a/packages/rc/.releaserc.json b/packages/rc/.releaserc.json new file mode 100644 index 00000000..dbc778df --- /dev/null +++ b/packages/rc/.releaserc.json @@ -0,0 +1,3 @@ +{ + "extends": "@anolilab/semantic-release-preset/npm" +} diff --git a/packages/rc/.secretlintignore b/packages/rc/.secretlintignore new file mode 100644 index 00000000..8d4c427f --- /dev/null +++ b/packages/rc/.secretlintignore @@ -0,0 +1,3 @@ +.pnpm-store +packages/**/node_modules +node_modules diff --git a/packages/rc/.secretlintrc.cjs b/packages/rc/.secretlintrc.cjs new file mode 100644 index 00000000..418ad9ce --- /dev/null +++ b/packages/rc/.secretlintrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + rules: [ + { + id: "@secretlint/secretlint-rule-preset-recommend", + }, + ], +}; diff --git a/packages/rc/CHANGELOG.md b/packages/rc/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/rc/LICENSE.md b/packages/rc/LICENSE.md new file mode 100644 index 00000000..a6b1fdfc --- /dev/null +++ b/packages/rc/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 anolilab + +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/packages/rc/README.md b/packages/rc/README.md new file mode 100644 index 00000000..c251e2e1 --- /dev/null +++ b/packages/rc/README.md @@ -0,0 +1,112 @@ +
+

anolilab rc

+

+ This module provides a utility function to load rc configuration settings from various sources, including environment variables, default values, and configuration files located in multiple standard directories. It merges these settings into a single configuration object. +

+
+ +
+ +
+ +[![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] + +
+ +--- + +
+

+ + Daniel Bannert's open source work is supported by the community on GitHub Sponsors + +

+
+ +--- + +## Install + +```sh +npm install @anolilab/rc +``` + +```sh +yarn add @anolilab/rc +``` + +```sh +pnpm add @anolilab/rc +``` + +## Usage + +The main function provided by this module is rc. It allows you to load configuration settings for your application from different sources. + +```ts +import { rc } from "@anolilab/rc"; + +const { config, files } = rc("npm"); + +// returns a merged config object with all found npmrc files and a files list what files where found. +``` + +### Api + +- name (string): The application name. This is used to locate configuration files and environment variables. +- options (object, optional): An object with the following properties: + - config (string, optional): Path to a specific configuration file. + - cwd (string, optional): The current working directory to start searching for configuration files. Defaults to process.cwd(). + - defaults (object, optional): Default configuration values. + - home (string, optional): The home directory to use. Defaults to os.homedir(). + - stopAt (string, optional): Directory to stop searching for configuration files. + +## Standards + +Given your application name (appname), rc will look in all the obvious places for configuration. + +- The defaults object you passed in +- `/etc/${appname}/config` +- `/etc/${appname}rc` +- `$HOME/.config/${appname}/config` +- `$HOME/.config/${appname}` +- `$HOME/.${appname}/config` +- `$HOME/.${appname}rc` +- a local `.${appname}/config` and `.${appname}rc` and all found looking in `../../../ ../../ ../ ./` etc. +- if you passed environment variable `${appname}_config` then from that file +- if you passed options.config variable, then from that file +- environment variables prefixed with `${appname}_` + or use "\_\_" to indicate nested properties
_(e.g. `appname_foo__bar__baz` => `foo.bar.baz`)_ + +All configuration sources that were found will be flattened into one object, in this exact order. + +## Related + +- [rc](https://github.com/dominictarr/rc) - The non-configurable configuration loader for lazy people. + +## Supported Node.js Versions + +Libraries in this ecosystem make the best effort to track [Node.js’ release schedule](https://github.com/nodejs/release#release-schedule). +Here’s [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a). + +## Contributing + +If you would like to help take a look at the [list of issues](https://github.com/anolilab/semantic-release/issues) and check our [Contributing](.github/CONTRIBUTING.md) guidelines. + +> **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. + +## Credits + +- [Daniel Bannert](https://github.com/prisis) +- [All Contributors](https://github.com/anolilab/semantic-release/graphs/contributors) + +## License + +The anolilab rc is open-sourced software licensed under the [MIT][license-url] + +[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript +[typescript-url]: "typescript" +[license-image]: https://img.shields.io/npm/l/@anolilab/rc?color=blueviolet&style=for-the-badge +[license-url]: LICENSE.md "license" +[npm-image]: https://img.shields.io/npm/v/@anolilab/rc/latest.svg?style=for-the-badge&logo=npm +[npm-url]: https://www.npmjs.com/package/@anolilab/rc/v/latest "npm" diff --git a/packages/rc/__tests__/rc-unmocked.test.ts b/packages/rc/__tests__/rc-unmocked.test.ts new file mode 100644 index 00000000..b6e9a282 --- /dev/null +++ b/packages/rc/__tests__/rc-unmocked.test.ts @@ -0,0 +1,104 @@ +import { rm } from "node:fs/promises"; +import { env } from "node:process"; + +import { writeJsonSync } from "@visulima/fs"; +import { join } from "@visulima/path"; +import { temporaryDirectory } from "tempy"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { rc } from "../src"; + +const mocks = vi.hoisted(() => { + return { mockedCwd: vi.fn(), mockedFindUpSync: vi.fn(), mockedHomeDir: vi.fn(), mockedIsAccessibleSync: vi.fn(), mockedReadFileSync: vi.fn() }; +}); + +vi.mock("node:os", () => { + return { + homedir: mocks.mockedHomeDir, + }; +}); + +vi.mock("node:process", async () => { + const actual = await vi.importActual("node:process"); + + return { + ...actual, + cwd: mocks.mockedCwd, + }; +}); + +describe("rc-unmocked", () => { + let cwdPath: string; + let homePath: string; + + const npmEnvironment: Record = {}; + + beforeEach(async () => { + cwdPath = temporaryDirectory(); + homePath = temporaryDirectory(); + + mocks.mockedCwd.mockReturnValue(cwdPath); + mocks.mockedHomeDir.mockReturnValue(homePath); + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const key in env) { + if (key.startsWith("npm_")) { + // eslint-disable-next-line security/detect-object-injection + npmEnvironment[key as keyof typeof env] = env[key]; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete,security/detect-object-injection + delete env[key]; + } + } + }); + + afterEach(async () => { + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax,guard-for-in + for (const key in npmEnvironment) { + // eslint-disable-next-line security/detect-object-injection + env[key] = npmEnvironment[key]; + } + + await rm(cwdPath, { recursive: true }); + await rm(homePath, { recursive: true }); + }); + + it("should find configs in different folders", () => { + expect.assertions(1); + + const files = [join("grandparent", "parent", "cwd", ".bemrc"), join("grandparent", "parent", ".bemrc"), join("grandparent", ".bemrc")]; + + files.forEach((file, index) => { + writeJsonSync(join(cwdPath, file), { test: index }); + }); + + mocks.mockedCwd.mockReturnValue(join(cwdPath, "grandparent", "parent", "cwd")); + + expect(rc("bem")).toStrictEqual({ + config: { + test: 0, + }, + files: files.map((file) => join(cwdPath, file)).reverse(), + }); + }); + + it("should find configs in custom cwd", () => { + expect.assertions(1); + + const files = [join("grandparent", "parent", "cwd", ".bemrc"), join("grandparent", "parent", ".bemrc"), join("grandparent", ".bemrc")]; + + files.forEach((file, index) => { + writeJsonSync(join(cwdPath, file), { test: index }); + }); + + expect( + rc("bem", { + cwd: join(cwdPath, "grandparent", "parent", "cwd"), + }), + ).toStrictEqual({ + config: { + test: 0, + }, + files: files.map((file) => join(cwdPath, file)).reverse(), + }); + }); +}); diff --git a/packages/rc/__tests__/rc.test.ts b/packages/rc/__tests__/rc.test.ts new file mode 100644 index 00000000..9659effc --- /dev/null +++ b/packages/rc/__tests__/rc.test.ts @@ -0,0 +1,393 @@ +import { rm } from "node:fs/promises"; +import { env } from "node:process"; + +import { join } from "@visulima/path"; +import { temporaryDirectory } from "tempy"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { rc } from "../src"; + +// Adds extention to the last item of an array representing fs path +const addExtensions = (sources: string[][]) => + // eslint-disable-next-line unicorn/no-array-reduce + sources.reduce((accumulator, pathArray) => { + ["", ".json"].forEach((extension) => { + const current = [pathArray].flat(); + current[current.length - 1] += extension; + accumulator.push(current); + }); + + return accumulator; + }, []); + +const mocks = vi.hoisted(() => { + return { mockedCwd: vi.fn(), mockedHomeDir: vi.fn(), mockedIsAccessibleSync: vi.fn(), mockedReadFileSync: vi.fn() }; +}); + +vi.mock("@visulima/fs", async () => { + const actual = await vi.importActual("@visulima/fs"); + + return { + ...actual, + isAccessibleSync: mocks.mockedIsAccessibleSync, + readFileSync: mocks.mockedReadFileSync, + }; +}); + +vi.mock("node:os", () => { + return { + homedir: mocks.mockedHomeDir, + }; +}); + +vi.mock("node:process", async () => { + const actual = await vi.importActual("node:process"); + + return { + ...actual, + cwd: mocks.mockedCwd, + }; +}); + +describe("rc", () => { + let cwdPath: string; + let homePath: string; + const npmEnvironment: Record = {}; + + beforeEach(async () => { + cwdPath = temporaryDirectory(); + homePath = temporaryDirectory(); + + mocks.mockedCwd.mockReturnValue(cwdPath); + mocks.mockedHomeDir.mockReturnValue(homePath); + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const key in env) { + if (key.startsWith("npm_")) { + // eslint-disable-next-line security/detect-object-injection + npmEnvironment[key as keyof typeof env] = env[key]; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete,security/detect-object-injection + delete env[key]; + } + } + }); + + afterEach(async () => { + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax,guard-for-in + for (const key in npmEnvironment) { + // eslint-disable-next-line security/detect-object-injection + env[key] = npmEnvironment[key]; + } + + await rm(cwdPath, { recursive: true }); + await rm(homePath, { recursive: true }); + }); + + it("should return config defaults", () => { + expect.assertions(1); + + mocks.mockedIsAccessibleSync.mockReturnValue(false); + + expect(rc("npm", { defaults: { test: 1 } })).toStrictEqual({ + config: { + test: 1, + }, + files: [], + }); + }); + + it("should find configs in home dir", () => { + expect.assertions(1); + + const sources = addExtensions([ + [".config", "bem", "config"], // ~/.config/bem/config + [".config", "bem"], // ~/.config/bem + [".bem", "config"], // ~/.bem/config + [".bemrc"], // ~/.bemrc + ]); + + sources.forEach((pathToConfig: string[], index: number) => { + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === join(homePath, ...pathToConfig)); + mocks.mockedReadFileSync.mockImplementation((file) => { + if (file === join(homePath, ...pathToConfig)) { + return JSON.stringify({ test: index }); + } + + return ""; + }); + }); + + expect(rc("bem")).toStrictEqual({ + config: { + test: 7, + }, + files: [join(homePath, ".bemrc.json")], + }); + }); + + it("should find config by ENV", () => { + expect.assertions(1); + + const filePath = "/test/.bemrc"; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === filePath); + mocks.mockedReadFileSync.mockImplementation((file) => { + if (file === filePath) { + return JSON.stringify({ test: 1 }); + } + + return ""; + }); + + env.bem_config = filePath; + + expect(rc("bem")).toStrictEqual({ + config: { + test: 1, + }, + files: ["/test/.bemrc"], + }); + + delete env.bem_config; + }); + + it("should use config field passed via ENV", () => { + expect.assertions(1); + + env.bem_test = "1"; + + mocks.mockedIsAccessibleSync.mockReturnValue(false); + + expect(rc("bem")).toStrictEqual({ config: { test: "1" }, files: [] }); + + delete env.bem_test; + }); + + it("should find config by ENV with different name", () => { + expect.assertions(1); + + const name = "ololo"; + + const filePath = "/test/.npmrc"; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === filePath); + mocks.mockedReadFileSync.mockImplementation((file) => { + if (file === filePath) { + return JSON.stringify({ test: 2 }); + } + + return ""; + }); + + env[name + "_test"] = "1"; + env[name + "_something__subtest"] = "1"; + + expect(rc(name)).toStrictEqual({ + config: { + something: { + subtest: "1", + }, + test: "1", + }, + files: [], + }); + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete env[name + "_test"]; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete env[name + "_something__subtest"]; + }); + + it("should find config in current folder", () => { + expect.assertions(1); + + const filePath = ".bemrc"; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === join(cwdPath, filePath)); + mocks.mockedReadFileSync.mockImplementation((file) => { + if (file === join(cwdPath, filePath)) { + return JSON.stringify({ test: 1 }); + } + + return ""; + }); + + expect(rc("bem")).toStrictEqual({ + config: { + test: 1, + }, + files: [join(cwdPath, filePath)], + }); + }); + + it("should find configs with different exts in current folder", () => { + expect.assertions(1); + + const files = [".bem/config", ".bem/config.json", ".bemrc", ".bemrc.json"]; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => { + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const path of files) { + if (file === join(cwdPath, path)) { + return true; + } + } + + return false; + }); + mocks.mockedReadFileSync.mockImplementation((file) => + files + .map((path, index) => { + if (file === join(cwdPath, path)) { + return JSON.stringify({ test: index }); + } + + return undefined; + }) + .filter(Boolean) + .join(""), + ); + + expect(rc("bem")).toStrictEqual({ + config: { test: 1 }, + files: [join(cwdPath, ".bemrc"), join(cwdPath, ".bemrc.json"), join(cwdPath, ".bem/config"), join(cwdPath, ".bem/config.json")], + }); + }); + + it("should find config with custom name in current folder", () => { + expect.assertions(1); + + const filePath = ".ololorc"; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === join(cwdPath, filePath)); + mocks.mockedReadFileSync.mockImplementation((file) => { + if (file === join(cwdPath, filePath)) { + return JSON.stringify({ test: 1 }); + } + + return ""; + }); + + expect(rc("ololo")).toStrictEqual({ + config: { + test: 1, + }, + files: [join(cwdPath, filePath)], + }); + }); + + it("should use .bemrc from /", () => { + expect.assertions(1); + + const filePath = ".bemrc"; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === join("/", filePath)); + mocks.mockedReadFileSync.mockImplementation(() => JSON.stringify({ test: "root" })); + + expect(rc("bem")).toStrictEqual({ config: { test: "root" }, files: ["/.bemrc"] }); + }); + + it("should filter same configs in proper order", () => { + expect.assertions(1); + + const filePath = ".bemrc"; + + mocks.mockedIsAccessibleSync.mockImplementation((file) => file === join("/", filePath) || file === join(homePath, filePath)); + mocks.mockedReadFileSync.mockImplementation((file) => { + if (file === join("/", filePath)) { + return JSON.stringify({ test: "root" }); + } + + if (file === join(homePath, filePath)) { + return JSON.stringify({ test: 1 }); + } + + return ""; + }); + + expect(rc("bem")).toStrictEqual({ + config: { + test: "root", + }, + files: [join(homePath, filePath), "/.bemrc"], + }); + }); + + it("should find different types of configs", () => { + expect.assertions(1); + + const files = { + "1": join(cwdPath, "grandparent", "parent", "cwd", ".bemrc"), + "2": join(cwdPath, "grandparent", "parent", ".bemrc"), + "3": join(cwdPath, "grandparent", ".bemrc"), + config: "/config/testfile", + env_config: "/env/config/.bemrc", + etc: "/etc/bemrc", + home: join(homePath, ".bemrc"), + root: "/.bemrc", + }; + + mocks.mockedCwd.mockReturnValue(join(cwdPath, "grandparent", "parent", "cwd")); + mocks.mockedIsAccessibleSync.mockImplementation((file) => { + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const path of Object.values(files)) { + if (file === path) { + return true; + } + } + + return false; + }); + mocks.mockedReadFileSync.mockImplementation((file) => + Object.entries(files) + .map(([value, path]) => { + if (file === path) { + return JSON.stringify({ last: value }); + } + + return undefined; + }) + .filter(Boolean) + .join(""), + ); + + env.bem_test = "env"; + env.bem_config = "/env/config/.bemrc"; + + /** + * 1 The defaults object you passed in + * 2 `/etc/${appname}/config` + * 3 `/etc/${appname}rc` + * 4 `$HOME/.config/${appname}/config` + * 5 `$HOME/.config/${appname}` + * 6 `$HOME/.${appname}/config` + * 7 `$HOME/.${appname}rc` + * 8 a local `.${appname}/config` and `.${appname}rc` and all found looking in `./ ../ ../../ ../../../` etc. + * 9 if you passed environment variable `${appname}_config` then from that file + * 10 if you passed options.config variable, then from that file + * 11 environment variables prefixed with `${appname}_` + * or use "\_\_" to indicate nested properties
_(e.g. `appname_foo__bar__baz` => `foo.bar.baz`)_ + */ + expect(rc("bem", { config: "/config/testfile", defaults: { default: "default" } })).toStrictEqual({ + config: { + default: "default", + last: "config", + test: "env", + }, + files: [ + // defaults + join("/", "etc", "bemrc"), // home + join(homePath, ".bemrc"), // home + join("/", ".bemrc"), // root or 1 + join(cwdPath, "grandparent", ".bemrc"), // 2 + join(cwdPath, "grandparent", "parent", ".bemrc"), // 3 + join(cwdPath, "grandparent", "parent", "cwd", ".bemrc"), // 4 + join("/", "env", "config", ".bemrc"), // env + "/config/testfile", // config + ], + }); + + delete env.bem_test; + delete env.bem_config; + }); +}); diff --git a/packages/rc/__tests__/utils/is-json.test.ts b/packages/rc/__tests__/utils/is-json.test.ts new file mode 100644 index 00000000..def16b3e --- /dev/null +++ b/packages/rc/__tests__/utils/is-json.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; + +import isJson from "../../src/utils/is-json"; + +describe("isJson", () => { + it.each([5, "5", "true", "null", "{}", '{"foo": "bar"}', "[1, 2, 3]", '{"foo": "bar", "baz": "qux"}'])("should return true for valid JSON", (value) => { + expect.assertions(1); + + expect(isJson(value)).toBeTruthy(); + }); + + // eslint-disable-next-line no-void + it.each(["NaN", "[", "{", "]", "}", "[{", "]}", "{[", "}]", void 0, Number.NaN, function noop() {}, [], {}, '{a":5}'])( + "should return false for invalid JSON %s", + (value) => { + expect.assertions(1); + + expect(isJson(value)).toBeFalsy(); + }, + ); +}); diff --git a/packages/rc/package.json b/packages/rc/package.json new file mode 100644 index 00000000..f55c5c3f --- /dev/null +++ b/packages/rc/package.json @@ -0,0 +1,129 @@ +{ + "name": "@anolilab/rc", + "version": "0.0.0", + "description": "The runtime configuration loader.", + "keywords": [ + "anolilab", + "rc" + ], + "homepage": "https://github.com/anolilab/semantic-release/tree/main/packages/rc", + "bugs": { + "url": "https://github.com/anolilab/semantic-release/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/anolilab/semantic-release.git", + "directory": "packages/rc" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/prisis" + }, + { + "type": "consulting", + "url": "https://anolilab.com/support" + } + ], + "license": "MIT", + "author": { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de" + }, + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + }, + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./package.json": "./package.json" + }, + "main": "dist/index.cjs", + "module": "dist/index.js", + "source": "src/index.ts", + "browser": "./dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], + "scripts": { + "build": "cross-env NODE_ENV=development tsup", + "build:prod": "cross-env NODE_ENV=production tsup", + "clean": "rimraf node_modules dist .eslintcache", + "dev": "pnpm run build --watch", + "lint:eslint": "eslint . --ext js,cjs,mjs,jsx,ts,tsx,json,yaml,yml,md,mdx --max-warnings=0 --config .eslintrc.cjs", + "lint:eslint:fix": "eslint . --ext js,cjs,mjs,jsx,ts,tsx,json,yaml,yml,md,mdx --max-warnings=0 --config .eslintrc.cjs --fix", + "lint:package-json": "publint --strict", + "lint:prettier": "prettier --config=.prettierrc.cjs --check .", + "lint:prettier:fix": "prettier --config=.prettierrc.cjs --write .", + "lint:types": "tsc --noEmit", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:ui": "vitest --ui --coverage.enabled=true", + "test:watch": "vitest" + }, + "dependencies": { + "@visulima/fs": "^2.1.1", + "ini": "^4.1.3", + "ts-deepmerge": "^7.0.0" + }, + "devDependencies": { + "@anolilab/eslint-config": "^15.0.3", + "@anolilab/prettier-config": "^5.0.14", + "@anolilab/semantic-release-preset": "^8.0.3", + "@babel/core": "^7.24.3", + "@rushstack/eslint-plugin-security": "^0.8.1", + "@secretlint/secretlint-rule-preset-recommend": "^8.1.2", + "@types/ini": "^4.1.0", + "@types/node": "18.18.14", + "@visulima/path": "^1.0.0", + "@vitest/coverage-v8": "^1.4.0", + "@vitest/ui": "^1.4.0", + "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "eslint-plugin-deprecation": "^2.0.0", + "eslint-plugin-etc": "^2.0.3", + "eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1", + "eslint-plugin-mdx": "^3.1.5", + "eslint-plugin-vitest": "^0.4.1", + "eslint-plugin-vitest-globals": "^1.5.0", + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "secretlint": "8.1.2", + "semantic-release": "^23.0.5", + "sort-package-json": "^2.8.0", + "tempy": "^3.1.0", + "tsup": "^8.0.2", + "typescript": "^5.4.3", + "vitest": "^1.4.0" + }, + "engines": { + "node": ">=18.* <=21.*" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "anolilab": { + "eslint-config": { + "plugin": { + "tsdoc": false, + "etc": false + }, + "warn_on_unsupported_typescript_version": false, + "info_on_disabling_jsx_react_rule": false, + "info_on_disabling_prettier_conflict_rule": false, + "info_on_disabling_jsonc_sort_keys_rule": false, + "info_on_disabling_etc_no_deprecated": false + } + } +} diff --git a/packages/rc/project.json b/packages/rc/project.json new file mode 100644 index 00000000..668269cd --- /dev/null +++ b/packages/rc/project.json @@ -0,0 +1,8 @@ +{ + "name": "rc", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/rc/src", + "projectType": "library", + "tags": ["rc", "type:package"], + "implicitDependencies": [] +} diff --git a/packages/rc/src/index.ts b/packages/rc/src/index.ts new file mode 100644 index 00000000..14da96f5 --- /dev/null +++ b/packages/rc/src/index.ts @@ -0,0 +1,213 @@ +import { homedir } from "node:os"; +import { cwd, env } from "node:process"; + +import { isAccessibleSync, readFileSync } from "@visulima/fs"; +import { parseJson, stripJsonComments } from "@visulima/fs/utils"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { dirname, join } from "@visulima/path"; +import { parse } from "ini"; +import { merge } from "ts-deepmerge"; + +import isJson from "./utils/is-json"; + +// eslint-disable-next-line no-secrets/no-secrets +/** + * Modified copy of the env function from https://github.com/dominictarr/rc/blob/a97f6adcc37ee1cad06ab7dc9b0bd842bbc5c664/lib/utils.js#L42 + * + * @license https://github.com/dominictarr/rc/blob/master/LICENSE.APACHE2 + * @license https://github.com/dominictarr/rc/blob/master/LICENSE.BSD + * @license https://github.com/dominictarr/rc/blob/master/LICENSE.MIT + * + * @param {string} prefix + * @param {Record} environment + * + * @returns {Record} + */ +// eslint-disable-next-line sonarjs/cognitive-complexity,@typescript-eslint/no-explicit-any +const getEnvironment = (prefix: string, environment: Record = env): Record => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const returnValue: Record = {}; + const l = prefix.length; + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const k in environment) { + if (k.toLowerCase().startsWith(prefix.toLowerCase())) { + const keypath = k.slice(Math.max(0, l)).split("__"); + + // Trim empty strings from keypath array + let emptyStringIndex; + + // eslint-disable-next-line no-loops/no-loops,no-cond-assign + while ((emptyStringIndex = keypath.indexOf("")) > -1) { + keypath.splice(emptyStringIndex, 1); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cursor: Record = returnValue; + + keypath.forEach((subkey, index) => { + // (check for subkey first so we ignore empty strings) + // (check for cursor to avoid assignment to primitive objects) + if (!subkey || typeof cursor !== "object") { + return; + } + + // If this is the last key, just stuff the value in there + // Assigns actual value from env variable to final key + // (unless it's just an empty string- in that case use the last valid key) + if (index === keypath.length - 1) { + // eslint-disable-next-line security/detect-object-injection + cursor[subkey] = environment[k]; + } + + // Build sub-object if nothing already exists at the keypath + // eslint-disable-next-line security/detect-object-injection + if (cursor[subkey] === undefined) { + // eslint-disable-next-line security/detect-object-injection + cursor[subkey] = {}; + } + + // Increment cursor used to track the object at the current depth + // eslint-disable-next-line security/detect-object-injection + cursor = cursor[subkey]; + }); + } + } + + return returnValue; +}; + +/** + * Will look in all the obvious places for configuration: + * + * - The defaults object you passed in + * - `/etc/${appname}/config` + * - `/etc/${appname}rc` + * - `$HOME/.config/${appname}/config` + * - `$HOME/.config/${appname}` + * - `$HOME/.${appname}/config` + * - `$HOME/.${appname}rc` + * - a local `.${appname}/config` and `.${appname}rc` and all found looking in `../../../ ../../ ../ ./` etc. + * - if you passed environment variable `${appname}_config` then from that file + * - if you passed options.config variable, then from that file + * - environment variables prefixed with `${appname}_` + * or use "\_\_" to indicate nested properties
_(e.g. `appname_foo__bar__baz` => `foo.bar.baz`)_ + * + * @param {string} name + * @param {string} home + * @param {string} internalCwd + * @param {string | undefined} stopAt + * @param {string | undefined} environmentConfig + * @param {string | undefined} optionConfig + * @returns {Array} + */ +// eslint-disable-next-line sonarjs/cognitive-complexity +const getConfigFiles = (name: string, home: string, internalCwd: string, stopAt?: string, environmentConfig?: string, optionConfig?: string): string[] => { + const configFiles = new Set(); + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const file of [`/etc/${name}/config`, `/etc/${name}rc`]) { + if (isAccessibleSync(file)) { + configFiles.add(file); + } + } + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const file of [join(home, ".config", name, "config"), join(home, ".config", name), join(home, `.${name}`, "config"), join(home, `.${name}rc`)]) { + if (isAccessibleSync(file)) { + configFiles.add(file); + } + + if (isAccessibleSync(`${file}.json`)) { + configFiles.add(`${file}.json`); + } + } + + let start = internalCwd; + let endOfLoop = false; + + const files = [join("." + name, "config.json"), join("." + name, "config"), join("." + name + "rc.json"), join("." + name + "rc")]; + + const traversedFiles: string[] = []; + + // eslint-disable-next-line no-loops/no-loops + do { + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const file of files) { + const traverseFile = join(start, file); + + if (isAccessibleSync(traverseFile)) { + traversedFiles.push(traverseFile); + } + } + + start = dirname(start); + + if (endOfLoop) { + break; + } + + endOfLoop = dirname(start) === start; + } while (stopAt ? start === stopAt : true); // root + + // reverse the traversedFiles so its starts with root + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const file of traversedFiles.reverse()) { + configFiles.add(file); + } + + if (typeof environmentConfig === "string" && isAccessibleSync(environmentConfig)) { + configFiles.add(environmentConfig); + } + + if (optionConfig && isAccessibleSync(optionConfig)) { + configFiles.add(optionConfig); + } + + return [...configFiles]; +}; + +// eslint-disable-next-line import/prefer-default-export +export const rc = ( + name: string, + options: { + config?: string; + cwd?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + defaults?: Record; + home?: string; + stopAt?: string; + } = {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): { config: Record; files: string[] } => { + // eslint-disable-next-line no-param-reassign + options = { + cwd: cwd(), + home: homedir(), + ...options, + }; + + const { config: environmentConfig, ...environment } = getEnvironment(`${name}_`); + + const configFiles = getConfigFiles(name, options.home as string, options.cwd as string, options.stopAt, env[`${name}_config`], options.config); + + const configs: object[] = []; + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const file of configFiles) { + const content = readFileSync(file); + + if (isJson(content)) { + configs.push(parseJson(stripJsonComments(content))); + } else { + configs.push(parse(content)); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (environment) { + configs.push(environment); + } + + return { config: merge(options.defaults ?? {}, ...configs), files: configFiles }; +}; diff --git a/packages/rc/src/utils/is-json.ts b/packages/rc/src/utils/is-json.ts new file mode 100644 index 00000000..62d32676 --- /dev/null +++ b/packages/rc/src/utils/is-json.ts @@ -0,0 +1,17 @@ +/** + * Checks if string is a parseable JSON string. + * + * @param {string} value + * @returns {boolean} + */ +const isJson = (value: string): boolean => { + try { + JSON.parse(value); + } catch { + return false; + } + + return true; +}; + +export default isJson; diff --git a/packages/rc/tsconfig.eslint.json b/packages/rc/tsconfig.eslint.json new file mode 100644 index 00000000..39eaefbf --- /dev/null +++ b/packages/rc/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": ["__docs__/**/*", "__tests__/**/*", "src/**/*", "*.d.ts", "tsup.config.ts"] +} diff --git a/packages/rc/tsconfig.json b/packages/rc/tsconfig.json new file mode 100644 index 00000000..f0ee80dc --- /dev/null +++ b/packages/rc/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "include": ["src/**/*", "*.d.ts"], + "compilerOptions": { + "moduleResolution": "bundler" + } +} diff --git a/packages/rc/tsup.config.ts b/packages/rc/tsup.config.ts new file mode 100644 index 00000000..bc5cb8dc --- /dev/null +++ b/packages/rc/tsup.config.ts @@ -0,0 +1,25 @@ +import type { Options } from "tsup"; +import { defineConfig } from "tsup"; + +// @ts-ignore +export default defineConfig((options: Options) => { + return { + ...options, + treeshake: true, + // react external https://github.com/vercel/turborepo/issues/360#issuecomment-1013885148 + external: ["semantic-release"], + silent: !options.watch, + minify: process.env["NODE_ENV"] === "production", + minifyWhitespace: process.env["NODE_ENV"] === "production", + incremental: !options.watch, + dts: true, + sourcemap: true, + clean: true, + splitting: true, + shims: true, + target: ["es2022", "node18"], + declaration: true, + entry: ["src/index.ts"], + format: ["esm", "cjs"], + }; +}); diff --git a/packages/rc/vitest.config.ts b/packages/rc/vitest.config.ts new file mode 100644 index 00000000..59d88c72 --- /dev/null +++ b/packages/rc/vitest.config.ts @@ -0,0 +1,5 @@ +import { getVitestConfig } from "../../tools/get-vitest-config"; + +const config = getVitestConfig(); + +export default config; diff --git a/packages/semantic-release-pnpm/.eslintignore b/packages/semantic-release-pnpm/.eslintignore new file mode 100644 index 00000000..207d83d7 --- /dev/null +++ b/packages/semantic-release-pnpm/.eslintignore @@ -0,0 +1,16 @@ +node_modules + +vitest.config.ts +.prettierrc.cjs +_meta.*.json +.secretlintrc.cjs + +coverage +__fixtures__ +dist + +tsup.config.ts + +src/definitions/context.ts + +__tests__/intigration/semantic-release-integration.test.ts diff --git a/packages/semantic-release-pnpm/.eslintrc.cjs b/packages/semantic-release-pnpm/.eslintrc.cjs new file mode 100644 index 00000000..d3fbfb8e --- /dev/null +++ b/packages/semantic-release-pnpm/.eslintrc.cjs @@ -0,0 +1,90 @@ +/** @ts-check */ +// eslint-disable-next-line import/no-commonjs +const { defineConfig } = require("@anolilab/eslint-config/define-config"); +// eslint-disable-next-line import/no-commonjs +const globals = require("@anolilab/eslint-config/globals"); + +/// +/// +/// +/// +/// + +/** @type {import('eslint').Linter.Config} */ +module.exports = defineConfig({ + env: { + // Your environments (which contains several predefined global variables) + // Most environments are loaded automatically if our rules are added + }, + extends: ["@anolilab/eslint-config", "@anolilab/eslint-config/typescript-type-checking"], + globals: { + ...globals.es2021, + // Your global variables (setting to false means it's not allowed to be reassigned) + // myGlobal: false + }, + ignorePatterns: ["!**/*"], + overrides: [ + { + files: ["*.ts", "*.tsx", "*.mts", "*.cts", "*.js", "*.jsx"], + // Set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting + parserOptions: {}, + rules: {}, + }, + { + files: ["*.ts", "*.tsx", "*.mts", "*.cts"], + // Set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting + parserOptions: {}, + rules: { + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "prefer-template": "off", + }, + }, + { + files: ["*.js", "*.jsx"], + rules: {}, + }, + { + files: ["*.mdx"], + rules: { + "jsx-a11y/anchor-has-content": "off", + // @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/917 + "jsx-a11y/heading-has-content": "off", + }, + }, + { + files: ["src/index.ts"], + rules: { + "import/no-unused-modules": "off", + }, + }, + { + files: ["__docs__/**"], + rules: { + "import/no-unresolved": "off", + "import/no-unused-modules": "off", + "no-console": "off", + "no-undef": "off", + "no-unused-vars": "off", + "unicorn/prefer-top-level-await": "off", + }, + }, + { + files: ["__tests__/**"], + rules: { + "import/no-unused-modules": "off", + }, + }, + ], + parserOptions: { + ecmaVersion: 2021, + project: "./tsconfig.eslint.json", + sourceType: "module", + }, + // Report unused `eslint-disable` comments. + reportUnusedDisableDirectives: true, + root: true, +}); diff --git a/packages/semantic-release-pnpm/.npmignore b/packages/semantic-release-pnpm/.npmignore new file mode 100644 index 00000000..ff4abfc4 --- /dev/null +++ b/packages/semantic-release-pnpm/.npmignore @@ -0,0 +1,9 @@ +package-lock.json + +src +__tests__ +__stories__ +__fixtures__ +.rpt2_cache +fixup.sh +.releaserc.json diff --git a/packages/semantic-release-pnpm/.prettierignore b/packages/semantic-release-pnpm/.prettierignore new file mode 100644 index 00000000..ebf52909 --- /dev/null +++ b/packages/semantic-release-pnpm/.prettierignore @@ -0,0 +1,9 @@ +.gitkeep +.env* +*.ico +*.lock +dist +CHANGELOG.md +coverage +node_modules +.eslintcache diff --git a/packages/semantic-release-pnpm/.prettierrc.cjs b/packages/semantic-release-pnpm/.prettierrc.cjs new file mode 100644 index 00000000..32f893c0 --- /dev/null +++ b/packages/semantic-release-pnpm/.prettierrc.cjs @@ -0,0 +1,5 @@ +const config = require("@anolilab/prettier-config"); + +module.exports = { + ...config, +}; diff --git a/packages/semantic-release-pnpm/.releaserc.json b/packages/semantic-release-pnpm/.releaserc.json new file mode 100644 index 00000000..dbc778df --- /dev/null +++ b/packages/semantic-release-pnpm/.releaserc.json @@ -0,0 +1,3 @@ +{ + "extends": "@anolilab/semantic-release-preset/npm" +} diff --git a/packages/semantic-release-pnpm/.secretlintignore b/packages/semantic-release-pnpm/.secretlintignore new file mode 100644 index 00000000..8d4c427f --- /dev/null +++ b/packages/semantic-release-pnpm/.secretlintignore @@ -0,0 +1,3 @@ +.pnpm-store +packages/**/node_modules +node_modules diff --git a/packages/semantic-release-pnpm/.secretlintrc.cjs b/packages/semantic-release-pnpm/.secretlintrc.cjs new file mode 100644 index 00000000..418ad9ce --- /dev/null +++ b/packages/semantic-release-pnpm/.secretlintrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + rules: [ + { + id: "@secretlint/secretlint-rule-preset-recommend", + }, + ], +}; diff --git a/packages/semantic-release-pnpm/CHANGELOG.md b/packages/semantic-release-pnpm/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/semantic-release-pnpm/LICENSE.md b/packages/semantic-release-pnpm/LICENSE.md new file mode 100644 index 00000000..a6b1fdfc --- /dev/null +++ b/packages/semantic-release-pnpm/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 anolilab + +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/packages/semantic-release-pnpm/README.md b/packages/semantic-release-pnpm/README.md new file mode 100644 index 00000000..884037f8 --- /dev/null +++ b/packages/semantic-release-pnpm/README.md @@ -0,0 +1,228 @@ +
+

anolilab semantic-release-pnpm

+

+ Semantic-release plugin to publish a npm package with pnpm. +

+
+ +
+ +
+ +[![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] + +
+ +--- + +
+

+ + Daniel Bannert's open source work is supported by the community on GitHub Sponsors + +

+
+ +--- + +## Install + +```sh +npm install @anolilab/semantic-release-pnpm +``` + +```sh +yarn add @anolilab/semantic-release-pnpm +``` + +```sh +pnpm add @anolilab/semantic-release-pnpm +``` + +## Usage + +The plugin can be configured in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration): + +```json +{ + "plugins": ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@anolilab/semantic-release-pnpm"] +} +``` + +## Steps that are used + +| Step | Description | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `verifyConditions` | Verify the presence of the `NPM_TOKEN` environment variable, or an `.npmrc` file, and verify the authentication method is valid. | +| `prepare` | Update the `package.json` version and [create](https://docs.npmjs.com/cli/pack) the npm package tarball. | +| `addChannel` | [Add a release to a dist-tag](https://docs.npmjs.com/cli/dist-tag). | +| `publish` | [Publish the npm package](https://docs.npmjs.com/cli/publish) to the registry. | + +## Configuration + +### npm registry authentication + +The npm [token](https://docs.npmjs.com/about-access-tokens) authentication configuration is **required** and can be set via [environment variables](#environment-variables). + +Automation tokens are recommended since they can be used for an automated workflow, even when your account is configured to use the [`auth-and-writes` level of 2FA](https://docs.npmjs.com/about-two-factor-authentication#authorization-and-writes). + +### npm provenance + +If you are publishing to the official registry and your pipeline is on a [provider that is supported by npm for provenance](https://docs.npmjs.com/generating-provenance-statements#provenance-limitations), npm can be configured to [publish with provenance](https://docs.npmjs.com/generating-provenance-statements). + +Since semantic-release wraps the npm publish command, configuring provenance is not exposed directly. +Instead, provenance can be configured through the [other configuration options exposed by npm](https://docs.npmjs.com/generating-provenance-statements#using-third-party-package-publishing-tools). +Provenance applies specifically to publishing, so our recommendation is to configure under `publishConfig` within the `package.json`. + +#### npm provenance on GitHub Actions + +For package provenance to be signed on the GitHub Actions CI the following permission is required +to be enabled on the job: + +```yaml +permissions: + id-token: write # to enable use of OIDC for npm provenance +``` + +It's worth noting that if you are using semantic-release to its fullest with a GitHub release, GitHub comments, +and other features, then [more permissions are required](https://github.com/semantic-release/github#github-authentication) to be enabled on this job: + +```yaml +permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + id-token: write # to enable use of OIDC for npm provenance +``` + +Refer to the [GitHub Actions recipe for npm package provenance](https://semantic-release.gitbook.io/semantic-release/recipes/ci-configurations/github-actions#.github-workflows-release.yml-configuration-for-node-projects) for the full CI job's YAML code example. + +### Environment variables + +| Variable | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `NPM_TOKEN` | Npm token created via [npm token create](https://docs.npmjs.com/getting-started/working_with_tokens#how-to-create-new-tokens) | + +### Options + +| Options | Description | Default | +| --------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `npmPublish` | Whether to publish the `npm` package to the registry. If `false` the `package.json` version will still be updated. | `false` if the `package.json` [private](https://docs.npmjs.com/files/package.json#private) property is `true`, `true` otherwise. | +| `pkgRoot` | Directory path to publish. | `.` | +| `tarballDir` | Directory path in which to write the package tarball. If `false` the tarball is not be kept on the file system. | `false` | +| `publishBranch` | The primary branch of the repository which is used for publishing the latest changes. | [master and main](https://pnpm.io/cli/publish#--publish-branch-branch) | + +**Note**: The `pkgRoot` directory must contain a `package.json`. The version will be updated only in the `package.json` and `npm-shrinkwrap.json` within the `pkgRoot` directory. + +**Note**: If you use a [shareable configuration](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/shareable-configurations.md#shareable-configurations) that defines one of these options you can set it to `false` in your [**semantic-release** configuration](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration) in order to use the default value. + +### npm configuration + +The plugin uses the [`npm` CLI](https://github.com/npm/cli) which will read the configuration from [`.npmrc`](https://docs.npmjs.com/files/npmrc). See [`npm config`](https://docs.npmjs.com/misc/config) for the option list. + +The [`registry`](https://docs.npmjs.com/misc/registry) can be configured via the npm environment variable `NPM_CONFIG_REGISTRY` and will take precedence over the configuration in `.npmrc`. + +The [`registry`](https://docs.npmjs.com/misc/registry) and [`dist-tag`](https://docs.npmjs.com/cli/dist-tag) can be configured under `publishConfig` in the `package.json`: + +```json +{ + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "tag": "latest" + } +} +``` + +**Notes**: + +- The presence of an `.npmrc` file will override any specified environment variables. +- The presence of `registry` or `dist-tag` under `publishConfig` in the `package.json` will take precedence over the configuration in `.npmrc` and `NPM_CONFIG_REGISTRY` + +### Examples + +The `npmPublish` and `tarballDir` option can be used to skip the publishing to the `npm` registry and instead, release the package tarball with another plugin. For example with the [@semantic-release/github](https://github.com/semantic-release/github) plugin: + +```json +{ + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@anolilab/semantic-release-pnpm", + { + "npmPublish": false, + "tarballDir": "dist" + } + ], + [ + "@semantic-release/github", + { + "assets": "dist/*.tgz" + } + ] + ] +} +``` + +When publishing from a sub-directory with the `pkgRoot` option, the `package.json` and `npm-shrinkwrap.json` updated with the new version can be moved to another directory with a `postversion`. For example with the [@semantic-release/git](https://github.com/semantic-release/git) plugin: + +```json +{ + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@anolilab/semantic-release-pnpm", + { + "pkgRoot": "dist" + } + ], + [ + "@semantic-release/git", + { + "assets": ["package.json", "npm-shrinkwrap.json"] + } + ] + ] +} +``` + +```json +{ + "scripts": { + "postversion": "cp -r package.json .. && cp -r npm-shrinkwrap.json .." + } +} +``` + +## Related + +- [@semantic-release/npm](https://github.com/semantic-release/npm) - 🚢 semantic-release plugin to publish a npm package +- [semantic-release-yarn](https://github.com/hongaar/semantic-release-yarn) - 🧶 A semantic-release plugin to publish npm packages with Yarn. Comes with built-in support for monorepos. + +## Supported Node.js Versions + +Libraries in this ecosystem make the best effort to track [Node.js’ release schedule](https://github.com/nodejs/release#release-schedule). +Here’s [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a). + +## Contributing + +If you would like to help take a look at the [list of issues](https://github.com/anolilab/semantic-release/issues) and check our [Contributing](.github/CONTRIBUTING.md) guidelines. + +> **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. + +## Credits + +- [Daniel Bannert](https://github.com/prisis) +- [All Contributors](https://github.com/anolilab/semantic-release/graphs/contributors) + +## License + +The anolilab semantic-release-pnpm is open-sourced software licensed under the [MIT][license-url] + +[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript +[typescript-url]: "typescript" +[license-image]: https://img.shields.io/npm/l/@anolilab/semantic-release-pnpm?color=blueviolet&style=for-the-badge +[license-url]: LICENSE.md "license" +[npm-image]: https://img.shields.io/npm/v/@anolilab/semantic-release-pnpm/latest.svg?style=for-the-badge&logo=npm +[npm-url]: https://www.npmjs.com/package/@anolilab/semantic-release-pnpm/v/latest "npm" diff --git a/packages/semantic-release-pnpm/__tests__/intigration/helpers/config.yaml b/packages/semantic-release-pnpm/__tests__/intigration/helpers/config.yaml new file mode 100644 index 00000000..ba3ebe7d --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/intigration/helpers/config.yaml @@ -0,0 +1,23 @@ +storage: "/verdaccio/storage/data" +plugins: "/verdaccio/plugins" +web: + title: "Verdaccio" +auth: + htpasswd: + file: "/verdaccio/storage/htpasswd" +packages: + "@*/*": + access: "$all" + publish: "$authenticated" + unpublish: "$authenticated" + "**": + access: "$all" + publish: "$authenticated" + unpublish: "$authenticated" +server: + keepAliveTimeout: 60 +middlewares: + audit: + enabled: true +logs: + - { type: "stdout", format: "pretty", level: "http" } diff --git a/packages/semantic-release-pnpm/__tests__/intigration/helpers/npm-registry.ts b/packages/semantic-release-pnpm/__tests__/intigration/helpers/npm-registry.ts new file mode 100644 index 00000000..bea75eac --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/intigration/helpers/npm-registry.ts @@ -0,0 +1,79 @@ +import { setTimeout } from "node:timers/promises"; +import { fileURLToPath } from "node:url"; + +import { dirname, join } from "@visulima/path"; +import Docker from "dockerode"; +import getStream from "get-stream"; +import { got } from "got"; +import pRetry from "p-retry"; + +const IMAGE = "verdaccio/verdaccio:4"; +const REGISTRY_PORT = 4873; +const REGISTRY_HOST = "localhost"; +const NPM_USERNAME = "integration"; +const NPM_PASSWORD = "suchsecure"; +const NPM_EMAIL = "integration@test.com"; + +const docker = new Docker(); + +let container: Docker.Container; + +/** + * Download the `npm-registry-docker` Docker image, create a new container and start it. + */ + +export const start = async (): Promise => { + await getStream(await docker.pull(IMAGE)); + + container = await docker.createContainer({ + Binds: [`${join(dirname(fileURLToPath(import.meta.url)), "config.yaml")}:/verdaccio/conf/config.yaml`], + Image: IMAGE, + PortBindings: { [`${REGISTRY_PORT}/tcp`]: [{ HostPort: `${REGISTRY_PORT}` }] }, + Tty: true, + }); + + await container.start(); + await setTimeout(4000); + + try { + // Wait for the registry to be ready + await pRetry(async () => await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, { cache: false }), { + factor: 2, + minTimeout: 1000, + retries: 7, + }); + } catch { + throw new Error(`Couldn't start npm-docker-couchdb after 2 min`); + } + + // Create user + await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/user/org.couchdb.user:${NPM_USERNAME}`, { + json: { + _id: `org.couchdb.user:${NPM_USERNAME}`, + email: NPM_EMAIL, + name: NPM_USERNAME, + password: NPM_PASSWORD, + roles: [], + type: "user", + }, + method: "PUT", + }); +}; + +export const url = `http://${REGISTRY_HOST}:${REGISTRY_PORT}/`; + +export const authEnvironment = { + NPM_EMAIL, + NPM_PASSWORD, + NPM_USERNAME, + npm_config_registry: url, +}; + +/** + * Stop and remote the `npm-registry-docker` Docker container. + */ + +export const stop = async (): Promise => { + await container.stop(); + await container.remove(); +}; diff --git a/packages/semantic-release-pnpm/__tests__/intigration/semantic-release-integration.test.ts b/packages/semantic-release-pnpm/__tests__/intigration/semantic-release-integration.test.ts new file mode 100644 index 00000000..82b71847 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/intigration/semantic-release-integration.test.ts @@ -0,0 +1,771 @@ +import { rm } from "node:fs/promises"; + +import { writeJson } from "@visulima/fs"; +import { join } from "@visulima/path"; +import { WritableStreamBuffer } from "stream-buffers"; +import { temporaryDirectory } from "tempy"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { start, stop, url as npmRegistryUrl } from "./helpers/npm-registry"; + +const logSpy = vi.fn(); +const logger = { error: vi.fn(), log: logSpy, success: vi.fn() }; + +// @TODO: port all test and try to run them +describe("semantic-release-integration", () => { + let cwd: string; + + beforeEach(async () => { + cwd = temporaryDirectory(); + + // Start the local NPM registry + await start(); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true }); + + // Stop the local NPM registry + await stop(); + }); + + // eslint-disable-next-line vitest/no-disabled-tests + it.skip('should skip npm auth verification if "npmPublish" is false', async () => { + expect.assertions(1); + + await writeJson(join(cwd, "package.json"), { name: "published", publishConfig: { registry: npmRegistryUrl }, version: "1.0.0" }); + + const { verifyConditions } = await import("../../src"); + + await expect( + async () => + await verifyConditions( + { npmPublish: false }, + { + cwd, + env: { NPM_TOKEN: "wrong_token" }, + logger, + options: {}, + stderr: new WritableStreamBuffer(), + stdout: new WritableStreamBuffer(), + }, + ), + ).rejects.not.toThrow("Invalid npm token."); + }); +}); + +// const path = require("node:path"); +// const test = require("ava"); +// const { outputJson, pathExists, readJson } = require("fs-extra"); +// const execa = require("execa"); +// const { spy } = require("sinon"); +// const tempy = require("tempy"); +// const clearModule = require("clear-module"); +// const { WritableStreamBuffer } = require("stream-buffers"); +// const npmRegistry = require("./helpers/npm-registry"); +// +// // Environment variables used only for the local npm command used to do verification +// const testEnvironment = { +// ...process.env, +// ...npmRegistry.authEnv, +// LEGACY_TOKEN: Buffer.from(`${npmRegistry.authEnv.NPM_USERNAME}:${npmRegistry.authEnv.NPM_PASSWORD}`, "utf8").toString("base64"), +// npm_config_registry: npmRegistry.url, +// }; + +// +// test('Skip npm auth verification if "package.private" is true', async (t) => { +// const cwd = tempy.directory(); +// const package_ = { name: "published", private: true, publishConfig: { registry: npmRegistry.url }, version: "1.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// await t.notThrowsAsync( +// t.context.m.verifyConditions( +// { npmPublish: false }, +// { +// cwd, +// env: {}, +// logger: t.context.logger, +// options: { publish: ["@semantic-release/npm"] }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// ); +// }); +// +// test('Skip npm token verification if "package.private" is true', async (t) => { +// const cwd = tempy.directory(); +// const package_ = { name: "published", private: true, publishConfig: { registry: npmRegistry.url }, version: "1.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// await t.notThrowsAsync( +// t.context.m.verifyConditions( +// {}, +// { +// cwd, +// env: {}, +// logger: t.context.logger, +// options: { publish: ["@semantic-release/npm"] }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// ); +// }); +// +// test("Throws error if NPM token is invalid", async (t) => { +// const cwd = tempy.directory(); +// const environment = { DEFAULT_NPM_REGISTRY: npmRegistry.url, NPM_TOKEN: "wrong_token" }; +// const package_ = { name: "published", publishConfig: { registry: npmRegistry.url }, version: "1.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const [error] = await t.throwsAsync( +// t.context.m.verifyConditions({}, { cwd, env: environment, logger: t.context.logger, options: {}, stderr: t.context.stderr, stdout: t.context.stdout }), +// ); +// +// t.is(error.name, "SemanticReleaseError"); +// t.is(error.code, "EINVALIDNPMTOKEN"); +// t.is(error.message, "Invalid npm token."); +// }); +// +// test("Skip Token validation if the registry configured is not the default one", async (t) => { +// const cwd = tempy.directory(); +// const environment = { NPM_TOKEN: "wrong_token" }; +// const package_ = { name: "published", publishConfig: { registry: "http://custom-registry.com/" }, version: "1.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// await t.notThrowsAsync( +// t.context.m.verifyConditions({}, { cwd, env: environment, logger: t.context.logger, options: {}, stderr: t.context.stderr, stdout: t.context.stdout }), +// ); +// }); +// +// test("Verify npm auth and package", async (t) => { +// const cwd = tempy.directory(); +// const package_ = { name: "valid-token", publishConfig: { registry: npmRegistry.url }, version: "0.0.0-dev" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// await t.notThrowsAsync( +// t.context.m.verifyConditions( +// {}, +// { +// cwd, +// env: npmRegistry.authEnv, +// logger: t.context.logger, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// ); +// }); +// +// test("Verify npm auth and package from a sub-directory", async (t) => { +// const cwd = tempy.directory(); +// const package_ = { name: "valid-token", publishConfig: { registry: npmRegistry.url }, version: "0.0.0-dev" }; +// await outputJson(path.resolve(cwd, "dist/package.json"), package_); +// await t.notThrowsAsync( +// t.context.m.verifyConditions( +// { pkgRoot: "dist" }, +// { +// cwd, +// env: npmRegistry.authEnv, +// logger: t.context.logger, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// ); +// }); +// +// test('Verify npm auth and package with "npm_config_registry" env var set by yarn', async (t) => { +// const cwd = tempy.directory(); +// const package_ = { name: "valid-token", publishConfig: { registry: npmRegistry.url }, version: "0.0.0-dev" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// await t.notThrowsAsync( +// t.context.m.verifyConditions( +// {}, +// { +// cwd, +// env: { ...npmRegistry.authEnv, npm_config_registry: "https://registry.yarnpkg.com" }, +// logger: t.context.logger, +// options: { publish: [] }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// ); +// }); +// +// test("Throw SemanticReleaseError Array if config option are not valid in verifyConditions", async (t) => { +// const cwd = tempy.directory(); +// const package_ = { publishConfig: { registry: npmRegistry.url } }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// const npmPublish = 42; +// const tarballDir = 42; +// const packageRoot = 42; +// const errors = [ +// ...(await t.throwsAsync( +// t.context.m.verifyConditions( +// {}, +// { +// cwd, +// env: {}, +// logger: t.context.logger, +// options: { +// publish: ["@semantic-release/github", { npmPublish, path: "@semantic-release/npm", pkgRoot: packageRoot, tarballDir }], +// }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// )), +// ]; +// +// t.is(errors[0].name, "SemanticReleaseError"); +// t.is(errors[0].code, "EINVALIDNPMPUBLISH"); +// t.is(errors[1].name, "SemanticReleaseError"); +// t.is(errors[1].code, "EINVALIDTARBALLDIR"); +// t.is(errors[2].name, "SemanticReleaseError"); +// t.is(errors[2].code, "EINVALIDPKGROOT"); +// t.is(errors[3].name, "SemanticReleaseError"); +// t.is(errors[3].code, "ENOPKG"); +// }); +// +// test("Publish the package", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "publish", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const result = await t.context.m.publish( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.deepEqual(result, { channel: "latest", name: "npm package (@latest dist-tag)", url: undefined }); +// t.is((await readJson(path.resolve(cwd, "package.json"))).version, "1.0.0"); +// t.false(await pathExists(path.resolve(cwd, `${package_.name}-1.0.0.tgz`))); +// t.is((await execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })).stdout, "1.0.0"); +// }); +// +// test("Publish the package on a dist-tag", async (t) => { +// const cwd = tempy.directory(); +// const environment = { ...npmRegistry.authEnv, DEFAULT_NPM_REGISTRY: npmRegistry.url }; +// const package_ = { name: "publish-tag", publishConfig: { registry: npmRegistry.url, tag: "next" }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const result = await t.context.m.publish( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { channel: "next", version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.deepEqual(result, { +// channel: "next", +// name: "npm package (@next dist-tag)", +// url: "https://www.npmjs.com/package/publish-tag/v/1.0.0", +// }); +// t.is((await readJson(path.resolve(cwd, "package.json"))).version, "1.0.0"); +// t.false(await pathExists(path.resolve(cwd, `${package_.name}-1.0.0.tgz`))); +// t.is((await execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })).stdout, "1.0.0"); +// }); +// +// test("Publish the package from a sub-directory", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "publish-sub-dir", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "dist/package.json"), package_); +// +// const result = await t.context.m.publish( +// { pkgRoot: "dist" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.deepEqual(result, { channel: "latest", name: "npm package (@latest dist-tag)", url: undefined }); +// t.is((await readJson(path.resolve(cwd, "dist/package.json"))).version, "1.0.0"); +// t.false(await pathExists(path.resolve(cwd, `${package_.name}-1.0.0.tgz`))); +// t.is((await execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })).stdout, "1.0.0"); +// }); +// +// test('Create the package and skip publish ("npmPublish" is false)', async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "skip-publish", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const result = await t.context.m.publish( +// { npmPublish: false, tarballDir: "tarball" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.false(result); +// t.is((await readJson(path.resolve(cwd, "package.json"))).version, "1.0.0"); +// t.true(await pathExists(path.resolve(cwd, `tarball/${package_.name}-1.0.0.tgz`))); +// await t.throwsAsync(execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })); +// }); +// +// test('Create the package and skip publish ("package.private" is true)', async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { +// name: "skip-publish-private", +// private: true, +// publishConfig: { registry: npmRegistry.url }, +// version: "0.0.0", +// }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const result = await t.context.m.publish( +// { tarballDir: "tarball" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.false(result); +// t.is((await readJson(path.resolve(cwd, "package.json"))).version, "1.0.0"); +// t.true(await pathExists(path.resolve(cwd, `tarball/${package_.name}-1.0.0.tgz`))); +// await t.throwsAsync(execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })); +// }); +// +// test('Create the package and skip publish from a sub-directory ("npmPublish" is false)', async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "skip-publish-sub-dir", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "dist/package.json"), package_); +// +// const result = await t.context.m.publish( +// { npmPublish: false, pkgRoot: "./dist", tarballDir: "./tarball" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.false(result); +// t.is((await readJson(path.resolve(cwd, "dist/package.json"))).version, "1.0.0"); +// t.true(await pathExists(path.resolve(cwd, `tarball/${package_.name}-1.0.0.tgz`))); +// await t.throwsAsync(execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })); +// }); +// +// test('Create the package and skip publish from a sub-directory ("package.private" is true)', async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { +// name: "skip-publish-sub-dir-private", +// private: true, +// publishConfig: { registry: npmRegistry.url }, +// version: "0.0.0", +// }; +// await outputJson(path.resolve(cwd, "dist/package.json"), package_); +// +// const result = await t.context.m.publish( +// { pkgRoot: "./dist", tarballDir: "./tarball" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.false(result); +// t.is((await readJson(path.resolve(cwd, "dist/package.json"))).version, "1.0.0"); +// t.true(await pathExists(path.resolve(cwd, `tarball/${package_.name}-1.0.0.tgz`))); +// await t.throwsAsync(execa("npm", ["view", package_.name, "version"], { cwd, env: testEnvironment })); +// }); +// +// test("Throw SemanticReleaseError Array if config option are not valid in publish", async (t) => { +// const cwd = tempy.directory(); +// const package_ = { publishConfig: { registry: npmRegistry.url } }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// const npmPublish = 42; +// const tarballDir = 42; +// const packageRoot = 42; +// +// const errors = [ +// ...(await t.throwsAsync( +// t.context.m.publish( +// { npmPublish, pkgRoot: packageRoot, tarballDir }, +// { +// cwd, +// env: {}, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: { publish: ["@semantic-release/github", "@semantic-release/npm"] }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// )), +// ]; +// +// t.is(errors[0].name, "SemanticReleaseError"); +// t.is(errors[0].code, "EINVALIDNPMPUBLISH"); +// t.is(errors[1].name, "SemanticReleaseError"); +// t.is(errors[1].code, "EINVALIDTARBALLDIR"); +// t.is(errors[2].name, "SemanticReleaseError"); +// t.is(errors[2].code, "EINVALIDPKGROOT"); +// t.is(errors[3].name, "SemanticReleaseError"); +// t.is(errors[3].code, "ENOPKG"); +// }); +// +// test("Prepare the package", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "prepare", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// await t.context.m.prepare( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.is((await readJson(path.resolve(cwd, "package.json"))).version, "1.0.0"); +// t.false(await pathExists(path.resolve(cwd, `${package_.name}-1.0.0.tgz`))); +// }); +// +// test("Prepare the package from a sub-directory", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "prepare-sub-dir", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "dist/package.json"), package_); +// +// await t.context.m.prepare( +// { pkgRoot: "dist" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.is((await readJson(path.resolve(cwd, "dist/package.json"))).version, "1.0.0"); +// t.false(await pathExists(path.resolve(cwd, `${package_.name}-1.0.0.tgz`))); +// }); +// +// test("Throw SemanticReleaseError Array if config option are not valid in prepare", async (t) => { +// const cwd = tempy.directory(); +// const package_ = { publishConfig: { registry: npmRegistry.url } }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// const npmPublish = 42; +// const tarballDir = 42; +// const packageRoot = 42; +// +// const errors = [ +// ...(await t.throwsAsync( +// t.context.m.prepare( +// { npmPublish, pkgRoot: packageRoot, tarballDir }, +// { +// cwd, +// env: {}, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: { publish: ["@semantic-release/github", "@semantic-release/npm"] }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// )), +// ]; +// +// t.is(errors[0].name, "SemanticReleaseError"); +// t.is(errors[0].code, "EINVALIDNPMPUBLISH"); +// t.is(errors[1].name, "SemanticReleaseError"); +// t.is(errors[1].code, "EINVALIDTARBALLDIR"); +// t.is(errors[2].name, "SemanticReleaseError"); +// t.is(errors[2].code, "EINVALIDPKGROOT"); +// t.is(errors[3].name, "SemanticReleaseError"); +// t.is(errors[3].code, "ENOPKG"); +// }); +// +// test("Publish the package and add to default dist-tag", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "add-channel", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// await t.context.m.publish( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { channel: "next", version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// const result = await t.context.m.addChannel( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.deepEqual(result, { channel: "latest", name: "npm package (@latest dist-tag)", url: undefined }); +// t.is((await execa("npm", ["view", package_.name, "dist-tags.latest"], { cwd, env: environment })).stdout, "1.0.0"); +// }); +// +// test("Publish the package and add to lts dist-tag", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "add-channel-legacy", publishConfig: { registry: npmRegistry.url }, version: "1.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// await t.context.m.publish( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { channel: "latest", version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// const result = await t.context.m.addChannel( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { channel: "1.x", version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.deepEqual(result, { channel: "release-1.x", name: "npm package (@release-1.x dist-tag)", url: undefined }); +// t.is((await execa("npm", ["view", package_.name, "dist-tags"], { cwd, env: environment })).stdout, "{ latest: '1.0.0', 'release-1.x': '1.0.0' }"); +// }); +// +// test('Skip adding the package to a channel ("npmPublish" is false)', async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "skip-add-channel", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const result = await t.context.m.addChannel( +// { npmPublish: false }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.false(result); +// await t.throwsAsync(execa("npm", ["view", package_.name, "version"], { cwd, env: environment })); +// }); +// +// test('Skip adding the package to a channel ("package.private" is true)', async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { +// name: "skip-add-channel-private", +// private: true, +// publishConfig: { registry: npmRegistry.url }, +// version: "0.0.0", +// }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// const result = await t.context.m.addChannel( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.false(result); +// await t.throwsAsync(execa("npm", ["view", package_.name, "version"], { cwd, env: environment })); +// }); +// +// test("Create the package in addChannel step", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "add-channel-pkg", publishConfig: { registry: npmRegistry.url }, version: "0.0.0" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// await t.context.m.prepare( +// { npmPublish: false, tarballDir: "tarball" }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.is((await readJson(path.resolve(cwd, "package.json"))).version, "1.0.0"); +// t.true(await pathExists(path.resolve(cwd, `tarball/${package_.name}-1.0.0.tgz`))); +// }); +// +// test("Throw SemanticReleaseError Array if config option are not valid in addChannel", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { publishConfig: { registry: npmRegistry.url } }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// const npmPublish = 42; +// const tarballDir = 42; +// const packageRoot = 42; +// +// const errors = [ +// ...(await t.throwsAsync( +// t.context.m.addChannel( +// { npmPublish, pkgRoot: packageRoot, tarballDir }, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: { publish: ["@semantic-release/github", "@semantic-release/npm"] }, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ), +// )), +// ]; +// +// t.is(errors[0].name, "SemanticReleaseError"); +// t.is(errors[0].code, "EINVALIDNPMPUBLISH"); +// t.is(errors[1].name, "SemanticReleaseError"); +// t.is(errors[1].code, "EINVALIDTARBALLDIR"); +// t.is(errors[2].name, "SemanticReleaseError"); +// t.is(errors[2].code, "EINVALIDPKGROOT"); +// t.is(errors[3].name, "SemanticReleaseError"); +// t.is(errors[3].code, "ENOPKG"); +// }); +// +// test("Verify token and set up auth only on the fist call, then prepare on prepare call only", async (t) => { +// const cwd = tempy.directory(); +// const environment = npmRegistry.authEnv; +// const package_ = { name: "test-module", publishConfig: { registry: npmRegistry.url }, version: "0.0.0-dev" }; +// await outputJson(path.resolve(cwd, "package.json"), package_); +// +// await t.notThrowsAsync( +// t.context.m.verifyConditions({}, { cwd, env: environment, logger: t.context.logger, options: {}, stderr: t.context.stderr, stdout: t.context.stdout }), +// ); +// await t.context.m.prepare( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// let result = await t.context.m.publish( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { channel: "next", version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// t.deepEqual(result, { channel: "next", name: "npm package (@next dist-tag)", url: undefined }); +// t.is((await execa("npm", ["view", package_.name, "dist-tags.next"], { cwd, env: environment })).stdout, "1.0.0"); +// +// result = await t.context.m.addChannel( +// {}, +// { +// cwd, +// env: environment, +// logger: t.context.logger, +// nextRelease: { version: "1.0.0" }, +// options: {}, +// stderr: t.context.stderr, +// stdout: t.context.stdout, +// }, +// ); +// +// t.deepEqual(result, { channel: "latest", name: "npm package (@latest dist-tag)", url: undefined }); +// t.is((await execa("npm", ["view", package_.name, "dist-tags.latest"], { cwd, env: environment })).stdout, "1.0.0"); +// }); diff --git a/packages/semantic-release-pnpm/__tests__/unit/prepare.test.ts b/packages/semantic-release-pnpm/__tests__/unit/prepare.test.ts new file mode 100644 index 00000000..cdc4ee74 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/prepare.test.ts @@ -0,0 +1,147 @@ +import { rm } from "node:fs/promises"; + +import { isAccessible, readFile, readJson, writeFile, writeJson } from "@visulima/fs"; +import type { PackageJson } from "@visulima/package"; +import { join } from "@visulima/path"; +import { execa } from "execa"; +import { WritableStreamBuffer } from "stream-buffers"; +import { temporaryDirectory } from "tempy"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { PrepareContext } from "../../src/definitions/context"; +import prepare from "../../src/prepare"; + +const logSpy = vi.fn(); +const logger = { error: vi.fn(), log: logSpy, success: vi.fn() }; + +describe("prepare", () => { + let cwd: string; + + beforeEach(async () => { + cwd = temporaryDirectory(); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true }); + }); + + it("should update package.json", async () => { + expect.assertions(2); + + const packagePath = join(cwd, "package.json"); + + await writeJson(packagePath, { name: "test", version: "0.0.0-dev" }); + + await prepare({}, { + cwd, + env: {}, + logger, + nextRelease: { version: "1.0.0" }, + stderr: new WritableStreamBuffer(), + stdout: new WritableStreamBuffer(), + } as unknown as PrepareContext); + + const packageJson = await readJson(packagePath); + + expect(packageJson.version).toBe("1.0.0"); + expect(logSpy).toHaveBeenCalledWith("Write version %s to package.json in %s", "1.0.0", cwd); + }); + + it("should update package.json and npm-shrinkwrap.json", async () => { + expect.assertions(3); + + const packagePath = join(cwd, "package.json"); + const shrinkwrapPath = join(cwd, "npm-shrinkwrap.json"); + + await writeJson(packagePath, { version: "0.0.0-dev" }); + // Create a npm-shrinkwrap.json file + await execa("npm", ["shrinkwrap"], { cwd }); + + await prepare({}, { + cwd, + env: {}, + logger, + nextRelease: { version: "1.0.0" }, + stderr: new WritableStreamBuffer(), + stdout: new WritableStreamBuffer(), + } as unknown as PrepareContext); + + const packageJson = await readJson(packagePath); + const shrinkwrap = await readJson>(shrinkwrapPath); + + expect(packageJson.version).toBe("1.0.0"); + expect(shrinkwrap.version).toBe("1.0.0"); + expect(logSpy).toHaveBeenCalledWith("Write version %s to package.json in %s", "1.0.0", cwd); + }); + + it("should preserve indentation and newline", async () => { + expect.assertions(2); + + const packagePath = join(cwd, "package.json"); + await writeFile(packagePath, `{\r\n "name": "package-name",\r\n "version": "0.0.0-dev"\r\n}\r\n`); + + await prepare({}, { + cwd, + env: {}, + logger, + nextRelease: { version: "1.0.0" }, + stderr: new WritableStreamBuffer(), + stdout: new WritableStreamBuffer(), + } as unknown as PrepareContext); + + const expectedContent = `{\r\n "name": "package-name",\r\n "version": "1.0.0"\r\n}\r\n`; + + expect(logSpy).toHaveBeenCalledWith("Write version %s to package.json in %s", "1.0.0", cwd); + await expect(readFile(packagePath)).resolves.toStrictEqual(expectedContent); + }); + + it('should create the package in the "tarballDir" directory', async () => { + expect.assertions(3); + + const packagePath = join(cwd, "package.json"); + const packageInfo = { name: "my-pkg", version: "0.0.0-dev" }; + + await writeJson(packagePath, packageInfo); + + await prepare({ tarballDir: "tarball" }, { + cwd, + env: {}, + logger, + nextRelease: { version: "1.0.0" }, + stderr: new WritableStreamBuffer(), + stdout: new WritableStreamBuffer(), + } as unknown as PrepareContext); + + const tarballPath = join(cwd, `tarball/${packageInfo.name}-1.0.0.tgz`); + const packageJson = await readJson(packagePath); + + expect(logSpy).toHaveBeenCalledWith("Write version %s to package.json in %s", "1.0.0", cwd); + expect(packageJson.version).toBe("1.0.0"); + await expect(isAccessible(tarballPath)).resolves.toBeTruthy(); + }); + + it('should only move the created tarball if the "tarballDir" directory is not the CWD', async () => { + expect.assertions(3); + + const packagePath = join(cwd, "package.json"); + const packageInfo = { name: "my-pkg", version: "0.0.0-dev" }; + + await writeJson(packagePath, packageInfo); + + await prepare({ tarballDir: "." }, { + cwd, + env: {}, + logger, + nextRelease: { version: "1.0.0" }, + stderr: new WritableStreamBuffer(), + stdout: new WritableStreamBuffer(), + } as unknown as PrepareContext); + + const tarballPath = join(cwd, `${packageInfo.name}-1.0.0.tgz`); + const packageJson = await readJson(packagePath); + + expect(logSpy).toHaveBeenCalledWith("Write version %s to package.json in %s", "1.0.0", cwd); + expect(packageJson.version).toBe("1.0.0"); + await expect(isAccessible(tarballPath)).resolves.toBeTruthy(); + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/get-channel.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/get-channel.test.ts new file mode 100644 index 00000000..a8bc5ac5 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/get-channel.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; + +import getChannel from "../../../src/utils/get-channel"; + +describe("getChannel", () => { + it("get default channel", () => { + expect.assertions(1); + + expect(getChannel(undefined)).toBe("latest"); + }); + + it("get passed channel if valid", () => { + expect.assertions(1); + + expect(getChannel("next")).toBe("next"); + }); + + it('prefix channel with "release-" if invalid', () => { + expect.assertions(2); + + expect(getChannel("1.x")).toBe("release-1.x"); + expect(getChannel("1.0.0")).toBe("release-1.0.0"); + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/get-npmrc-path.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/get-npmrc-path.test.ts new file mode 100644 index 00000000..1f3efb1d --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/get-npmrc-path.test.ts @@ -0,0 +1,108 @@ +import type SemanticReleaseError from "@semantic-release/error"; +import { ensureFileSync } from "@visulima/fs"; +import type AggregateError from "aggregate-error"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import getNpmrcPath from "../../../src/utils/get-npmrc-path"; + +const mocks = vi.hoisted(() => { + return { + mockedEnsureFileSync: vi.fn(), + mockedFindCacheDirectorySync: vi.fn(), + mockedIsAccessibleSync: vi.fn(), + mockedResolve: vi.fn(), + }; +}); + +vi.mock("@visulima/fs", () => { + return { + ensureFileSync: mocks.mockedEnsureFileSync, + isAccessibleSync: mocks.mockedIsAccessibleSync, + }; +}); + +vi.mock("@visulima/package", () => { + return { + findCacheDirectorySync: mocks.mockedFindCacheDirectorySync, + }; +}); + +vi.mock("@visulima/path", () => { + return { + resolve: mocks.mockedResolve, + }; +}); + +describe("getNpmrcPath", () => { + const cwd = "/test/directory"; + const npmrcPath = "/test/directory/.npmrc"; + + beforeEach(() => { + vi.clearAllMocks(); + + mocks.mockedResolve.mockReturnValue(npmrcPath); + }); + + it("should return NPM_CONFIG_USERCONFIG if it is set and accessible", () => { + expect.assertions(2); + + const environment = { NPM_CONFIG_USERCONFIG: "/custom/path/.npmrc" }; + + mocks.mockedIsAccessibleSync.mockReturnValueOnce(true); + + const result = getNpmrcPath(cwd, environment); + + expect(result).toBe(environment.NPM_CONFIG_USERCONFIG); + expect(mocks.mockedIsAccessibleSync).toHaveBeenCalledWith(environment.NPM_CONFIG_USERCONFIG); + }); + + it("should return the resolved .npmrc path if it is accessible", () => { + expect.assertions(2); + + const environment = {}; + + mocks.mockedIsAccessibleSync.mockImplementation((path: string) => path === npmrcPath); + + const result = getNpmrcPath(cwd, environment); + + expect(result).toBe(npmrcPath); + expect(mocks.mockedIsAccessibleSync).toHaveBeenCalledWith(npmrcPath); + }); + + it("should create and return a temporary npmrc path if no other .npmrc is accessible", () => { + expect.assertions(3); + + const environment = {}; + const temporaryNpmrcPath = "/temporary/directory/.npmrc"; + + mocks.mockedIsAccessibleSync.mockReturnValue(false); + mocks.mockedFindCacheDirectorySync.mockReturnValue(temporaryNpmrcPath); + + const result = getNpmrcPath(cwd, environment); + + expect(result).toBe(temporaryNpmrcPath); + expect(mocks.mockedFindCacheDirectorySync).toHaveBeenCalledWith("semantic-release-pnpm", { create: true, cwd }); + expect(ensureFileSync).toHaveBeenCalledWith(temporaryNpmrcPath); + }); + + it("should throw an error if no .npmrc is found or accessible", () => { + expect.assertions(2); + + const environment = {}; + + mocks.mockedIsAccessibleSync.mockReturnValue(false); + mocks.mockedFindCacheDirectorySync.mockReturnValue(undefined); + + try { + getNpmrcPath(cwd, environment); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typedError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect((typedError.errors[0] as SemanticReleaseError).message).toBe("Missing `.npmrc` file."); + // eslint-disable-next-line vitest/no-conditional-expect + expect((typedError.errors[0] as SemanticReleaseError).code).toBe("ENOPNPMRC"); + } + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/get-pkg.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/get-pkg.test.ts new file mode 100644 index 00000000..d9dc5901 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/get-pkg.test.ts @@ -0,0 +1,92 @@ +// eslint-disable-next-line unicorn/prevent-abbreviations +import { rm } from "node:fs/promises"; + +import { writeFile, writeJson } from "@visulima/fs"; +import { resolve } from "@visulima/path"; +import { temporaryDirectory } from "tempy"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import getPackage from "../../../src/utils/get-pkg"; + +describe("get-pkg", () => { + let cwd: string; + + beforeEach(async () => { + cwd = temporaryDirectory(); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true }); + }); + + it("verify name and version then return parsed package.json", async () => { + expect.assertions(2); + + const packageJson = { name: "package", version: "0.0.0" }; + await writeJson(resolve(cwd, "package.json"), packageJson); + + const result = await getPackage({}, { cwd }); + + expect(result.name).toBe(packageJson.name); + expect(result.version).toBe(packageJson.version); + }); + + it("verify name and version then return parsed package.json from a sub-directory", async () => { + expect.assertions(2); + + const packageRoot = "dist"; + const packageJson = { name: "package", version: "0.0.0" }; + await writeJson(resolve(cwd, packageRoot, "package.json"), packageJson); + + const result = await getPackage({ pkgRoot: packageRoot }, { cwd }); + + expect(result.name).toBe(packageJson.name); + expect(result.version).toBe(packageJson.version); + }); + + it("throw error if missing package.json", async () => { + expect.assertions(3); + + try { + await getPackage({}, { cwd }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typeError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.name).toBe("AggregateError"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.message).toContain("Missing `package.json` file."); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.errors[0].code).toBe("ENOPKG"); + } + }); + + it("throw error if missing package name", async () => { + expect.assertions(3); + + await writeJson(resolve(cwd, "package.json"), { version: "0.0.0" }); + + try { + await getPackage({}, { cwd }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typeError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.name).toBe("AggregateError"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.message).toContain("Missing `name` property in `package.json`"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.errors[0].code).toBe("ENOPKGNAME"); + } + }); + + it("throw error if package.json is malformed", async () => { + expect.assertions(1); + + await writeFile(resolve(cwd, "package.json"), "{name: 'package',}"); + + await expect(getPackage({}, { cwd })).rejects.toThrow("JSON at position 1"); + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/get-registry.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/get-registry.test.ts new file mode 100644 index 00000000..9c821f3d --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/get-registry.test.ts @@ -0,0 +1,78 @@ +import { rm } from "node:fs/promises"; + +import { writeFile } from "@visulima/fs"; +import { resolve } from "@visulima/path"; +import { temporaryDirectory } from "tempy"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import getRegistry from "../../../src/utils/get-registry"; + +describe("getRegistry", () => { + let cwd: string; + + beforeEach(async () => { + cwd = temporaryDirectory(); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true }); + }); + + it("get default registry", () => { + expect.assertions(2); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "package-name" }, { cwd, env: {} } as any)).toBe("https://registry.npmjs.org/"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "package-name", publishConfig: {} }, { cwd, env: {} } as any)).toBe("https://registry.npmjs.org/"); + }); + + it('get the registry configured in ".npmrc" and normalize trailing slash', async () => { + expect.assertions(1); + + await writeFile(resolve(cwd, ".npmrc"), "registry = https://custom1.registry.com"); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "package-name" }, { cwd, env: {} } as any)).toBe("https://custom1.registry.com/"); + }); + + it('get the registry configured from "publishConfig"', async () => { + expect.assertions(1); + + await writeFile(resolve(cwd, ".npmrc"), "registry = https://custom2.registry.com"); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "package-name", publishConfig: { registry: "https://custom3.registry.com/" } }, { cwd, env: {} } as any)).toBe( + "https://custom3.registry.com/", + ); + }); + + it('get the registry configured in "NPM_CONFIG_REGISTRY"', () => { + expect.assertions(1); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "package-name" }, { cwd, env: { NPM_CONFIG_REGISTRY: "https://custom1.registry.com/" } } as any)).toBe( + "https://custom1.registry.com/", + ); + }); + + it('get the registry configured in ".npmrc" for scoped package', async () => { + expect.assertions(1); + + await writeFile(resolve(cwd, ".npmrc"), "@scope:registry = https://custom3.registry.com"); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "@scope/package-name" }, { cwd, env: {} } as any)).toBe("https://custom3.registry.com/"); + }); + + it('get the registry configured via "NPM_CONFIG_USERCONFIG" for scoped package', async () => { + expect.assertions(1); + + await writeFile(resolve(cwd, ".custom-npmrc"), "@scope:registry = https://custom4.registry.com"); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(getRegistry({ name: "@scope/package-name" }, { cwd, env: { NPM_CONFIG_USERCONFIG: resolve(cwd, ".custom-npmrc") } } as any)).toBe( + "https://custom4.registry.com/", + ); + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/get-release-info.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/get-release-info.test.ts new file mode 100644 index 00000000..e0b26e32 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/get-release-info.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; + +import type { PublishContext } from "../../../src/definitions/context"; +import { getReleaseInfo } from "../../../src/utils/get-release-info"; + +describe("getReleaseInfo", () => { + it("default registry and scoped module", async () => { + expect.assertions(1); + + expect( + getReleaseInfo( + { name: "@scope/module" }, + { env: {}, nextRelease: { version: "1.0.0" } } as PublishContext, + "latest", + "https://registry.npmjs.org/", + ), + ).toStrictEqual({ + channel: "latest", + name: "pnpm package (@latest dist-tag)", + url: "https://www.npmjs.com/package/@scope/module/v/1.0.0", + }); + }); + + it("custom registry and scoped module", async () => { + expect.assertions(1); + + expect( + getReleaseInfo( + { name: "@scope/module" }, + { env: {}, nextRelease: { version: "1.0.0" } } as PublishContext, + "latest", + "https://custom.registry.org/", + ), + ).toStrictEqual({ + channel: "latest", + name: "pnpm package (@latest dist-tag)", + url: undefined, + }); + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/set-npmrc-auth.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/set-npmrc-auth.test.ts new file mode 100644 index 00000000..4029ad9f --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/set-npmrc-auth.test.ts @@ -0,0 +1,311 @@ +import { rm } from "node:fs/promises"; +import { env } from "node:process"; + +import { readFile, writeFile } from "@visulima/fs"; +import { resolve } from "@visulima/path"; +import { temporaryDirectory, temporaryFile } from "tempy"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const setNpmrcAuthFilePath = "../../../src/utils/set-npmrc-auth"; + +const logSpy = vi.fn(); +const logger = { log: logSpy }; + +const homeDirectorySpy = vi.fn(); + +vi.mock("node:os", () => { + return { + homedir: homeDirectorySpy, + }; +}); + +describe("set-npmrc-auth", () => { + let cwd: string; + let home: string; + const npmEnvironment: Record = {}; + + beforeEach(() => { + cwd = temporaryDirectory(); + home = temporaryDirectory(); + + homeDirectorySpy.mockReturnValue(home); + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const key in env) { + if (key.startsWith("npm_")) { + // eslint-disable-next-line security/detect-object-injection + npmEnvironment[key as keyof typeof env] = env[key]; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete,security/detect-object-injection + delete env[key]; + } + } + }); + + afterEach(async () => { + vi.resetAllMocks(); + + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax,guard-for-in + for (const key in npmEnvironment) { + // eslint-disable-next-line security/detect-object-injection + env[key] = npmEnvironment[key]; + } + + await rm(cwd, { recursive: true }); + await rm(home, { recursive: true }); + }); + + it('should set auth with "NPM_TOKEN"', async () => { + expect.assertions(2); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: { NPM_TOKEN: "npm_token" }, logger }); + + expect(logSpy).toHaveBeenCalledWith(`Wrote NPM_TOKEN to ${npmrc}`); + + // eslint-disable-next-line no-template-curly-in-string + await expect(readFile(npmrc)).resolves.toBe("registry=https://registry.npmjs.org/\n\n//custom.registry.com/:_authToken = ${NPM_TOKEN}"); + }); + + it('should set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', async () => { + expect.assertions(2); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { + cwd, + env: { NPM_EMAIL: "npm_email", NPM_PASSWORD: "npm_pasword", NPM_USERNAME: "npm_username" }, + logger, + }); + + expect(logSpy).toHaveBeenCalledWith(`Wrote NPM_USERNAME, NPM_PASSWORD, and NPM_EMAIL to ${npmrc}`); + + await expect(readFile(npmrc)).resolves.toBe(`registry=https://registry.npmjs.org/\n\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`); + }); + + it('should preserve home ".npmrc"', async () => { + expect.assertions(3); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + await writeFile(resolve(home, ".npmrc"), "home_config = test"); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: { NPM_TOKEN: "npm_token" }, logger }); + + expect(logSpy.mock.calls[1]).toStrictEqual(["Reading npm config from %s", [resolve(home, ".npmrc")].join(", ")]); + expect(logSpy.mock.calls[2]).toStrictEqual([`Wrote NPM_TOKEN to ${npmrc}`]); + + await expect(readFile(npmrc)).resolves.toBe( + `registry=https://registry.npmjs.org/\nhome_config=test\n\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`, + ); + }); + + it('should preserve home and local ".npmrc"', async () => { + expect.assertions(3); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + await writeFile(resolve(cwd, ".npmrc"), "cwd_config = test"); + await writeFile(resolve(home, ".npmrc"), "home_config = test"); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: { NPM_TOKEN: "npm_token" }, logger }); + + expect(logSpy.mock.calls[1]).toStrictEqual(["Reading npm config from %s", [resolve(home, ".npmrc"), resolve(cwd, ".npmrc")].join(", ")]); + expect(logSpy.mock.calls[2]).toStrictEqual([`Wrote NPM_TOKEN to ${npmrc}`]); + + await expect(readFile(npmrc)).resolves.toBe( + `registry=https://registry.npmjs.org/\nhome_config=test\ncwd_config=test\n\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`, + ); + }); + + it('should preserve all ".npmrc" if auth is already configured', async () => { + expect.assertions(2); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + await writeFile(resolve(cwd, ".npmrc"), `//custom.registry.com/:_authToken = \${NPM_TOKEN}`); + await writeFile(resolve(home, ".npmrc"), "home_config = test"); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: {}, logger }); + + expect(logSpy.mock.calls[1]).toStrictEqual(["Reading npm config from %s", [resolve(home, ".npmrc"), resolve(cwd, ".npmrc")].join(", ")]); + await expect(readFile(npmrc)).resolves.toBe( + `registry=https://registry.npmjs.org/\nhome_config=test\n//custom.registry.com/:_authToken=\${NPM_TOKEN}\n`, + ); + }); + + it('should preserve ".npmrc" if auth is already configured for a scoped package', async () => { + expect.assertions(2); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + await writeFile(resolve(cwd, ".npmrc"), `@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`); + await writeFile(resolve(home, ".npmrc"), "home_config = test"); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: {}, logger }); + + expect(logSpy.mock.calls[1]).toStrictEqual(["Reading npm config from %s", [resolve(home, ".npmrc"), resolve(cwd, ".npmrc")].join(", ")]); + await expect(readFile(npmrc)).resolves.toBe( + `registry=https://registry.npmjs.org/\nhome_config=test\n@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken=\${NPM_TOKEN}\n`, + ); + }); + + it('should throw error if "NPM_TOKEN" is missing', async () => { + expect.assertions(3); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + const errorMessage = "No npm token specified."; + + try { + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: {}, logger }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typeError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.name).toBe("AggregateError"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.message).toContain(errorMessage); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.errors[0].code).toBe("ENONPMTOKEN"); + } + }); + + it('should emulate npm config resolution if "NPM_CONFIG_USERCONFIG" is set', async () => { + expect.assertions(2); + + const npmrc = temporaryFile({ name: ".npmrc" }); + + const customNpmrcPath = resolve(cwd, ".custom-npmrc"); + await writeFile(customNpmrcPath, `//custom.registry.com/:_authToken = \${NPM_TOKEN}`); + + const environment = { NPM_CONFIG_USERCONFIG: customNpmrcPath }; + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { + cwd, + env: environment, + logger, + }); + + expect(logSpy.mock.calls[1]).toStrictEqual(["Reading npm config from %s", customNpmrcPath]); + await expect(readFile(npmrc)).resolves.toBe(`registry=https://registry.npmjs.org/\n//custom.registry.com/:_authToken=\${NPM_TOKEN}\n`); + }); + + it('should throw error if "NPM_USERNAME" is missing', async () => { + expect.assertions(3); + + const npmrc = temporaryFile({ name: ".npmrc" }); + const environment = { NPM_EMAIL: "npm_email", NPM_PASSWORD: "npm_password" }; + + const errorMessage = "No npm token specified."; + + try { + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: environment, logger }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typeError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.name).toBe("AggregateError"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.message).toContain(errorMessage); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.errors[0].code).toBe("ENONPMTOKEN"); + } + }); + + it('should throw error if "NPM_PASSWORD" is missing', async () => { + expect.assertions(3); + + const npmrc = temporaryFile({ name: ".npmrc" }); + const environment = { NPM_EMAIL: "npm_email", NPM_USERNAME: "npm_username" }; + + const errorMessage = "No npm token specified."; + + try { + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: environment, logger }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typeError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.name).toBe("AggregateError"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.message).toContain(errorMessage); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.errors[0].code).toBe("ENONPMTOKEN"); + } + }); + + it('should throw error if "NPM_EMAIL" is missing', async () => { + expect.assertions(3); + + const npmrc = temporaryFile({ name: ".npmrc" }); + const environment = { NPM_PASSWORD: "npm_password", NPM_USERNAME: "npm_username" }; + + const errorMessage = "No npm token specified."; + + try { + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://custom.registry.com", { cwd, env: environment, logger }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typeError = error as AggregateError; + + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.name).toBe("AggregateError"); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.message).toContain(errorMessage); + // eslint-disable-next-line vitest/no-conditional-expect + expect(typeError.errors[0].code).toBe("ENONPMTOKEN"); + } + }); + + it("prefer .npmrc over environment variables", async () => { + expect.assertions(4); + + const npmrc = temporaryFile({ name: ".npmrc" }); + // Specify an NPM token environment variable + const environment = { NPM_TOKEN: "env_npm_token" }; + + await writeFile(resolve(cwd, ".npmrc"), "//registry.npmjs.org/:_authToken=npmrc_npm_token"); + + const setNpmrcAuth = await import(setNpmrcAuthFilePath).then((m) => m.default); + + await setNpmrcAuth(npmrc, "http://registry.npmjs.org", { cwd, env: environment, logger }); + + // Assert that the token from .npmrc is used + await expect(readFile(npmrc)).resolves.toBe(`registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=npmrc_npm_token\n`); + + // Assert that the log indicates reading from .npmrc + expect(logSpy.mock.calls[1]).toStrictEqual(["Reading npm config from %s", resolve(cwd, ".npmrc")]); + + // Assert that NPM_TOKEN is not written + // eslint-disable-next-line no-loops/no-loops,no-restricted-syntax + for (const log of logSpy.mock.calls) { + expect(log).not.toStrictEqual(expect.stringContaining("Wrote NPM_TOKEN")); + } + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/utils/should-publish.test.ts b/packages/semantic-release-pnpm/__tests__/unit/utils/should-publish.test.ts new file mode 100644 index 00000000..21dac8a3 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/utils/should-publish.test.ts @@ -0,0 +1,97 @@ +import type { PackageJson } from "@visulima/package"; +import { describe, expect, it } from "vitest"; + +import type { PluginConfig } from "../../../src/definitions/plugin-config"; +import { reasonToNotPublish, shouldPublish } from "../../../src/utils/should-publish"; + +describe("reasonToNotPublish", () => { + it('should return "npmPublish plugin option is false" when npmPublish is false', () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: false }; + const packageJson: PackageJson = { name: "test-package" }; + + const result = reasonToNotPublish(pluginConfig, packageJson); + + expect(result).toBe("npmPublish plugin option is false"); + }); + + it('should return "package is private and has no workspaces" when package is private and has no workspaces', () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: true }; + const packageJson: PackageJson = { name: "test-package", private: true }; + + const result = reasonToNotPublish(pluginConfig, packageJson); + + expect(result).toBe("package is private and has no workspaces"); + }); + + it("should return null when npmPublish is true, package is not private, and has no workspaces", () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: true }; + const packageJson: PackageJson = { name: "test-package" }; + + const result = reasonToNotPublish(pluginConfig, packageJson); + + expect(result).toBeNull(); + }); + + it("should return null when package is private but has workspaces", () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: true }; + const packageJson: PackageJson = { name: "test-package", private: true, workspaces: ["packages/*"] }; + + const result = reasonToNotPublish(pluginConfig, packageJson); + + expect(result).toBeNull(); + }); +}); + +describe("shouldPublish", () => { + it("should return false when npmPublish is false", () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: false }; + const packageJson: PackageJson = { name: "test-package" }; + + const result = shouldPublish(pluginConfig, packageJson); + + expect(result).toBeFalsy(); + }); + + it("should return false when package is private and has no workspaces", () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: true }; + const packageJson: PackageJson = { name: "test-package", private: true }; + + const result = shouldPublish(pluginConfig, packageJson); + + expect(result).toBeFalsy(); + }); + + it("should return true when npmPublish is true, package is not private, and has no workspaces", () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: true }; + const packageJson: PackageJson = { name: "test-package" }; + + const result = shouldPublish(pluginConfig, packageJson); + + expect(result).toBeTruthy(); + }); + + it("should return true when package is private but has workspaces", () => { + expect.assertions(1); + + const pluginConfig: PluginConfig = { npmPublish: true }; + const packageJson: PackageJson = { name: "test-package", private: true, workspaces: ["packages/*"] }; + + const result = shouldPublish(pluginConfig, packageJson); + + expect(result).toBeTruthy(); + }); +}); diff --git a/packages/semantic-release-pnpm/__tests__/unit/verify/verify-config.test.ts b/packages/semantic-release-pnpm/__tests__/unit/verify/verify-config.test.ts new file mode 100644 index 00000000..0d1f5df7 --- /dev/null +++ b/packages/semantic-release-pnpm/__tests__/unit/verify/verify-config.test.ts @@ -0,0 +1,81 @@ +import type SemanticReleaseError from "@semantic-release/error"; +import { describe, expect, it } from "vitest"; + +import type { PluginConfig } from "../../../src/definitions/plugin-config"; +import verifyConfig from "../../../src/verify/verify-config"; + +describe("verify-config", () => { + it('verify "npmPublish", "tarballDir" and "pkgRoot" options', async () => { + expect.assertions(1); + + expect(verifyConfig({ npmPublish: true, pkgRoot: "dist", tarballDir: "release" })).toStrictEqual([]); + }); + + it('return SemanticReleaseError if "npmPublish" option is not a Boolean', async () => { + expect.assertions(3); + + const npmPublish = 42; + const [error, ...errors] = verifyConfig({ npmPublish } as unknown as PluginConfig); + + expect(errors).toHaveLength(0); + expect((error as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error as SemanticReleaseError).code).toBe("EINVALIDNPMPUBLISH"); + }); + + it('return SemanticReleaseError if "tarballDir" option is not a String', async () => { + expect.assertions(3); + + // eslint-disable-next-line unicorn/prevent-abbreviations + const tarballDir = 42; + const [error, ...errors] = verifyConfig({ tarballDir } as unknown as PluginConfig); + + expect(errors).toHaveLength(0); + expect((error as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error as SemanticReleaseError).code).toBe("EINVALIDTARBALLDIR"); + }); + + it('return SemanticReleaseError if "pkgRoot" option is not a String', async () => { + expect.assertions(3); + + const packageRoot = 42; + const [error, ...errors] = verifyConfig({ pkgRoot: packageRoot } as unknown as PluginConfig); + + expect(errors).toHaveLength(0); + expect((error as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error as SemanticReleaseError).code).toBe("EINVALIDPKGROOT"); + }); + + it('return SemanticReleaseError if "publishBranch" option is not a String', async () => { + expect.assertions(3); + + const publishBranch = 42; + const [error, ...errors] = verifyConfig({ publishBranch } as unknown as PluginConfig); + + expect(errors).toHaveLength(0); + expect((error as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error as SemanticReleaseError).code).toBe("EINVALIDPUBLISHBRANCH"); + }); + + it("return SemanticReleaseError Array if multiple config are invalid", async () => { + expect.assertions(8); + + const npmPublish = 42; + // eslint-disable-next-line unicorn/prevent-abbreviations + const tarballDir = 42; + const packageRoot = 42; + const publishBranch = 42; + const [error1, error2, error3, error4] = verifyConfig({ npmPublish, pkgRoot: packageRoot, publishBranch, tarballDir } as unknown as PluginConfig); + + expect((error1 as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error1 as SemanticReleaseError).code).toBe("EINVALIDNPMPUBLISH"); + + expect((error2 as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error2 as SemanticReleaseError).code).toBe("EINVALIDPKGROOT"); + + expect((error3 as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error3 as SemanticReleaseError).code).toBe("EINVALIDPUBLISHBRANCH"); + + expect((error4 as SemanticReleaseError).name).toBe("SemanticReleaseError"); + expect((error4 as SemanticReleaseError).code).toBe("EINVALIDTARBALLDIR"); + }); +}); diff --git a/packages/semantic-release-pnpm/package.json b/packages/semantic-release-pnpm/package.json new file mode 100644 index 00000000..aba66263 --- /dev/null +++ b/packages/semantic-release-pnpm/package.json @@ -0,0 +1,144 @@ +{ + "name": "@anolilab/semantic-release-pnpm", + "version": "0.0.0", + "description": "Semantic-release plugin to publish a npm package with pnpm.", + "keywords": [ + "anolilab", + "npm", + "pnpm", + "publish", + "monorepo", + "semantic-release", + "semantic-release-plugin", + "semantic-release-pnpm" + ], + "homepage": "https://github.com/anolilab/semantic-release/tree/main/packages/semantic-release-pnpm", + "bugs": { + "url": "https://github.com/anolilab/semantic-release/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/anolilab/semantic-release.git", + "directory": "packages/semantic-release-pnpm" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/prisis" + }, + { + "type": "consulting", + "url": "https://anolilab.com/support" + } + ], + "license": "MIT", + "author": { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de" + }, + "sideEffects": false, + "type": "module", + "exports": "./dist/index.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], + "scripts": { + "build": "cross-env NODE_ENV=development tsup", + "build:prod": "cross-env NODE_ENV=production tsup", + "clean": "rimraf node_modules dist .eslintcache", + "dev": "pnpm run build --watch", + "lint:eslint": "eslint . --ext js,cjs,mjs,jsx,ts,tsx,json,yaml,yml,md,mdx --max-warnings=0 --config .eslintrc.cjs", + "lint:eslint:fix": "eslint . --ext js,cjs,mjs,jsx,ts,tsx,json,yaml,yml,md,mdx --max-warnings=0 --config .eslintrc.cjs --fix", + "lint:package-json": "publint --strict", + "lint:prettier": "prettier --config=.prettierrc.cjs --check .", + "lint:prettier:fix": "prettier --config=.prettierrc.cjs --write .", + "lint:types": "tsc --noEmit", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:ui": "vitest --ui --coverage.enabled=true", + "test:watch": "vitest" + }, + "dependencies": { + "@anolilab/rc": "workspace:*", + "@semantic-release/error": "^4.0.0", + "@visulima/fs": "^2.1.1", + "@visulima/package": "1.8.1", + "@visulima/path": "^1.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.1.0", + "ini": "4.1.2", + "move-file": "^3.1.0", + "normalize-url": "^8.0.1", + "registry-auth-token": "^5.0.2", + "semver": "^7.6.2" + }, + "devDependencies": { + "@anolilab/eslint-config": "^15.0.3", + "@anolilab/prettier-config": "^5.0.14", + "@anolilab/semantic-release-preset": "^8.0.3", + "@babel/core": "^7.24.3", + "@rushstack/eslint-plugin-security": "^0.8.1", + "@secretlint/secretlint-rule-preset-recommend": "^8.1.2", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^10.0.4", + "@types/dockerode": "^3.3.29", + "@types/ini": "^4.1.0", + "@types/node": "18.18.14", + "@types/semantic-release__error": "3.0.3", + "@types/semver": "7.5.8", + "@types/stream-buffers": "^3.0.7", + "@vitest/coverage-v8": "^1.4.0", + "@vitest/ui": "^1.4.0", + "cross-env": "^7.0.3", + "dockerode": "4.0.2", + "eslint": "^8.57.0", + "eslint-plugin-deprecation": "^2.0.0", + "eslint-plugin-editorconfig": "^4.0.3", + "eslint-plugin-import": "npm:eslint-plugin-i@2.29.1", + "eslint-plugin-mdx": "^3.1.5", + "eslint-plugin-n": "^17.7.0", + "eslint-plugin-vitest": "^0.4.1", + "eslint-plugin-vitest-globals": "^1.5.0", + "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", + "get-stream": "9.0.1", + "got": "^14.2.1", + "p-retry": "^6.2.0", + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "secretlint": "8.1.2", + "semantic-release": "^23.0.5", + "sort-package-json": "^2.8.0", + "stream-buffers": "^3.0.2", + "tempy": "^3.1.0", + "tsup": "^8.0.2", + "typescript": "^5.4.3", + "vitest": "^1.4.0" + }, + "engines": { + "node": ">=18.* <=21.*" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "anolilab": { + "eslint-config": { + "plugin": { + "etc": false, + "tsdoc": false + }, + "warn_on_unsupported_typescript_version": false, + "info_on_disabling_jsx_react_rule": false, + "info_on_disabling_prettier_conflict_rule": false, + "info_on_disabling_jsonc_sort_keys_rule": false, + "import_ignore_exports": [ + "**/*.cjs" + ] + } + } +} diff --git a/packages/semantic-release-pnpm/project.json b/packages/semantic-release-pnpm/project.json new file mode 100644 index 00000000..218504a0 --- /dev/null +++ b/packages/semantic-release-pnpm/project.json @@ -0,0 +1,8 @@ +{ + "name": "semantic-release-pnpm", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/semantic-release-pnpm/src", + "projectType": "library", + "tags": ["semantic-release-pnpm", "type:package"], + "implicitDependencies": ["rc"] +} diff --git a/packages/semantic-release-pnpm/src/add-channel.ts b/packages/semantic-release-pnpm/src/add-channel.ts new file mode 100644 index 00000000..19bcfb8c --- /dev/null +++ b/packages/semantic-release-pnpm/src/add-channel.ts @@ -0,0 +1,50 @@ +import type { PackageJson } from "@visulima/package"; +import { execa } from "execa"; + +import type { AddChannelContext } from "./definitions/context"; +import type { PluginConfig } from "./definitions/plugin-config"; +import getChannel from "./utils/get-channel"; +import getNpmrcPath from "./utils/get-npmrc-path"; +import getRegistry from "./utils/get-registry"; +import type { ReleaseInfo } from "./utils/get-release-info"; +import { getReleaseInfo } from "./utils/get-release-info"; +import { reasonToNotPublish, shouldPublish } from "./utils/should-publish"; + +export default async (pluginConfig: PluginConfig, package_: PackageJson, context: AddChannelContext): Promise => { + const { + cwd, + env, + logger, + nextRelease: { channel, version }, + stderr, + stdout, + } = context; + + if (shouldPublish(pluginConfig, package_)) { + const registry = getRegistry(package_, context); + const distributionTag = getChannel(channel); + + logger.log(`Adding version ${version} to npm registry on dist-tag ${distributionTag}`); + + const npmrc = getNpmrcPath(cwd, env); + + const result = execa("pnpm", ["dist-tag", "add", `${package_.name}@${version}`, distributionTag, "--userconfig", npmrc, "--registry", registry], { + cwd, + env, + preferLocal: true, + }); + + result.stdout.pipe(stdout, { end: false }); + result.stderr.pipe(stderr, { end: false }); + + await result; + + logger.log(`Added ${package_.name}@${version} to dist-tag @${distributionTag} on ${registry}`); + + return getReleaseInfo(package_, context, distributionTag, registry); + } + + logger.log(`Skip adding to npm channel as ${reasonToNotPublish(pluginConfig, package_)}`); + + return false; +}; diff --git a/packages/semantic-release-pnpm/src/definitions/constants.ts b/packages/semantic-release-pnpm/src/definitions/constants.ts new file mode 100644 index 00000000..28c85eaa --- /dev/null +++ b/packages/semantic-release-pnpm/src/definitions/constants.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/"; diff --git a/packages/semantic-release-pnpm/src/definitions/context.ts b/packages/semantic-release-pnpm/src/definitions/context.ts new file mode 100644 index 00000000..a5c51e66 --- /dev/null +++ b/packages/semantic-release-pnpm/src/definitions/context.ts @@ -0,0 +1,135 @@ +import type stream from "node:stream"; + +import type { Commit, Options } from "semantic-release"; + +// @todo these types need testing + +/** + * Define context types. Documentation seems to not be up to date with the + * source code. + * + * Source: https://github.com/semantic-release/semantic-release/blob/master/index.js + * Docs: https://semantic-release.gitbook.io/semantic-release/developer-guide/plugin#context + */ +export interface CommonContext { + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L66 + branch: BranchSpec; + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L65 + branches: BranchSpec[]; + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L256-L260 + cwd: string; + env: typeof process.env; + + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L262 + logger: { + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/lib/get-logger.js#L12-L14 + error: (...message: string[]) => void; + log: (...message: string[]) => void; + success: (...message: string[]) => void; + }; + + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L267 + options: Options; + + stderr: stream.Writable; + + stdout: stream.Writable; +} + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L103 +export type VerifyConditionsContext = CommonContext; + +type CommonContext2 = CommonContext & { + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L106 + releases: Release[]; +}; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L118 +type GenerateNotesContext1 = CommonContext2 & { + commits: Commit[]; + lastRelease: Release; + nextRelease: Release; +}; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L140 +export type AddChannelContext = CommonContext2 & { + commits: Commit[]; + currentRelease: Release; + lastRelease: Release; + nextRelease: Release; +}; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L142 +type SuccessContext1 = CommonContext2 & { + commits: Commit[]; + lastRelease: Release; + nextRelease: Release; +}; + +type CommonContext3 = CommonContext2 & { + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L163 + commits: Commit[]; + + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#LL150C3-L150C10 + lastRelease: Release; +}; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L166 +export type AnalyzeCommitsContext = CommonContext3; + +type CommonContext4 = CommonContext3 & { + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L163 + commits: Commit[]; + + // https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L175 + nextRelease: Release; +}; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L189 +export type VerifyReleaseContext = CommonContext4; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L191 +type GenerateNotesContext2 = CommonContext4; + +export type GenerateNotesContext = GenerateNotesContext1 | GenerateNotesContext2; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L193 +export type PrepareContext = CommonContext4; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L206 +export type PublishContext = CommonContext4; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L209 +type SuccessContext2 = CommonContext4; + +export type SuccessContext = SuccessContext1 | SuccessContext2; + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L243 +export type FailContext = CommonContext & { + errors: unknown[]; +}; + +// @todo infer return type from https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/lib/branches/index.js#L70 +export interface BranchSpec { + name: string; + tags?: Tag[]; +} + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L133 +export interface Tag { + channel?: string; + gitHead?: string; + gitTag?: string; + version?: string; +} + +// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/lib/get-release-to-add.js#LL51C9-L56C25 +export interface Release { + channel?: string | null; + gitHead?: string; + gitTag?: string; + name?: string; + // https://github.com/sindresorhus/semver-diff#semverdiffversiona-versionb + type?: "build" | "major" | "minor" | "patch" | "premajor" | "preminor" | "prepatch" | "prerelease" | undefined; + version: string; +} diff --git a/packages/semantic-release-pnpm/src/definitions/errors.ts b/packages/semantic-release-pnpm/src/definitions/errors.ts new file mode 100644 index 00000000..34d835f2 --- /dev/null +++ b/packages/semantic-release-pnpm/src/definitions/errors.ts @@ -0,0 +1,126 @@ +import package_ from "../../package.json"; + +const linkify = (file: string) => `${package_.homepage}/blob/main/${file}`; + +interface ErrorDetails { + details: string; + message: string; +} + +export type ErrorDefinition = (context: ErrorContext) => ErrorDetails; + +export interface ErrorContext { + npmPublish?: boolean; + npmrc?: string; + pkgRoot?: string; + publishBranch?: string; + registry?: string; + tarballDir?: string; + version?: string; +} + +export const errors: { + EINVALIDBRANCHES: (branches: string[]) => { details: string; message: string }; + EINVALIDNPMPUBLISH: ({ npmPublish }: ErrorContext) => { details: string; message: string }; + EINVALIDNPMTOKEN: ({ registry }: ErrorContext) => { details: string; message: string }; + EINVALIDPKGROOT: ({ pkgRoot }: ErrorContext) => { details: string; message: string }; + EINVALIDPNPM: ({ version }: ErrorContext) => { details: string; message: string }; + EINVALIDPUBLISHBRANCH: ({ publishBranch }: ErrorContext) => { details: string; message: string }; + EINVALIDTARBALLDIR: ({ tarballDir }: ErrorContext) => { details: string; message: string }; + ENONPMTOKEN: ({ registry }: ErrorContext) => { details: string; message: string }; + ENOPKG: () => { details: string; message: string }; + ENOPKGNAME: () => { details: string; message: string }; + ENOPNPM: () => { details: string; message: string }; + ENOPNPMRC: () => { details: string; message: string }; +} = { + EINVALIDBRANCHES: (branches: string[]) => { + return { + details: `The [branches option](${linkify("README.md#branches")}) option, if defined, must be an array of \`String\`. +Your configuration for the \`branches\` option is \`${branches.join(",")}\`.`, + message: "Invalid `branches` option.", + }; + }, + EINVALIDNPMPUBLISH: ({ npmPublish }: ErrorContext) => { + return { + details: `The [npmPublish option](${linkify("README.md#npmpublish")}) option, if defined, must be a \`Boolean\`. +Your configuration for the \`npmPublish\` option is \`${npmPublish}\`.`, + message: "Invalid `npmPublish` option.", + }; + }, + EINVALIDNPMTOKEN: ({ registry }: ErrorContext) => { + return { + details: `The [npm token](${linkify( + "README.md#npm-registry-authentication", + )}) configured in the \`NPM_TOKEN\` environment variable must be a valid [token](https://docs.npmjs.com/getting-started/working_with_tokens) allowing to publish to the registry \`${registry}\`. +If you are using Two Factor Authentication for your account, set its level to ["Authorization only"](https://docs.npmjs.com/getting-started/using-two-factor-authentication#levels-of-authentication) in your account settings. **semantic-release** cannot publish with the default "Authorization and writes" level. +Please make sure to set the \`NPM_TOKEN\` environment variable in your CI with the exact value of the npm token.`, + message: "Invalid npm token.", + }; + }, + EINVALIDPKGROOT: ({ pkgRoot }: ErrorContext) => { + return { + details: `The [pkgRoot option](${linkify("README.md#pkgroot")}) option, if defined, must be a \`String\`. +Your configuration for the \`pkgRoot\` option is \`${pkgRoot}\`.`, + message: "Invalid `pkgRoot` option.", + }; + }, + EINVALIDPNPM: ({ version }: ErrorContext) => { + return { + details: `The version of Pnpm that you are using is not compatible. Please refer to [the README](${linkify( + "README.md#install", + )}) to review which versions of Pnpm are currently supported + +Your version of Pnpm is "${version}".`, + message: "Incompatible Pnpm version detected.", + }; + }, + EINVALIDPUBLISHBRANCH: ({ publishBranch }: ErrorContext) => { + return { + details: `The [publishBranch option](${linkify("README.md#publishBranch")}) option, if defined, must be a \`String\`. +Your configuration for the \`publishBranch\` option is \`${publishBranch}\`.`, + message: "Invalid `publishBranch` option.", + }; + }, + EINVALIDTARBALLDIR: ({ tarballDir }: ErrorContext) => { + return { + details: `The [tarballDir option](${linkify("README.md#tarballdir")}) option, if defined, must be a \`String\`. +Your configuration for the \`tarballDir\` option is \`${tarballDir}\`.`, + message: "Invalid `tarballDir` option.", + }; + }, + ENONPMTOKEN: ({ registry }: ErrorContext) => { + return { + details: `An [npm token](${linkify( + "README.md#npm-registry-authentication", + )}) must be created and set in the \`NPM_TOKEN\` environment variable on your CI environment. +Please make sure to create an [npm token](https://docs.npmjs.com/getting-started/working_with_tokens#how-to-create-new-tokens) and to set it in the \`NPM_TOKEN\` environment variable on your CI environment. The token must allow to publish to the registry \`${registry}\`.`, + message: "No npm token specified.", + }; + }, + ENOPKG: () => { + return { + details: `A [package.json file](https://docs.npmjs.com/files/package.json) at the root of your project is required to release on npm. +Please follow the [npm guideline](https://docs.npmjs.com/getting-started/creating-node-modules) to create a valid \`package.json\` file.`, + message: "Missing `package.json` file.", + }; + }, + ENOPKGNAME: () => { + return { + details: `The \`package.json\`'s [name](https://docs.npmjs.com/files/package.json#name) property is required in order to publish a package to the npm registry. +Please make sure to add a valid \`name\` for your package in your \`package.json\`.`, + message: "Missing `name` property in `package.json`.", + }; + }, + ENOPNPM: () => { + return { + details: `The Pnpm CLI could not be found in your PATH. Make sure Pnpm is installed and try again.`, + message: "Pnpm not found.", + }; + }, + ENOPNPMRC: () => { + return { + details: `Didnt find a \`.npmrc\` file or it was not possible to create , in the root of your project.`, + message: "Missing `.npmrc` file.", + }; + }, +}; diff --git a/packages/semantic-release-pnpm/src/definitions/plugin-config.ts b/packages/semantic-release-pnpm/src/definitions/plugin-config.ts new file mode 100644 index 00000000..e48551f0 --- /dev/null +++ b/packages/semantic-release-pnpm/src/definitions/plugin-config.ts @@ -0,0 +1,8 @@ +export interface PluginConfig { + branches?: (string | { name: string; prerelease: boolean })[]; + disableScripts?: boolean; + npmPublish?: boolean; + pkgRoot?: string; + publishBranch?: string; + tarballDir?: string; +} diff --git a/packages/semantic-release-pnpm/src/index.ts b/packages/semantic-release-pnpm/src/index.ts new file mode 100644 index 00000000..e19ee089 --- /dev/null +++ b/packages/semantic-release-pnpm/src/index.ts @@ -0,0 +1,75 @@ +import addChannelNpm from "./add-channel"; +import type { AddChannelContext, PrepareContext, PublishContext, VerifyConditionsContext } from "./definitions/context"; +import type { PluginConfig } from "./definitions/plugin-config"; +import prepareNpm from "./prepare"; +import publishNpm from "./publish"; +import getPackage from "./utils/get-pkg"; +import type { ReleaseInfo } from "./utils/get-release-info"; +import verify from "./verify"; + +const PLUGIN_NAME = "semantic-release-pnpm"; + +let verified: boolean; +let prepared: boolean; + +export const verifyConditions = async (pluginConfig: PluginConfig, context: VerifyConditionsContext): Promise => { + /** + * If the plugin is used and has `npmPublish`, `tarballDir` or + * `pkgRoot` configured, validate them now in order to prevent any release if + * the configuration is wrong + */ + if (context.options.publish) { + const publish = Array.isArray(context.options.publish) ? context.options.publish : [context.options.publish]; + + const publishPlugin = publish.find((config) => config.path && config.path === PLUGIN_NAME) || {}; + + // eslint-disable-next-line no-param-reassign + pluginConfig.npmPublish = pluginConfig.npmPublish ?? publishPlugin.npmPublish; + // eslint-disable-next-line no-param-reassign + pluginConfig.tarballDir = pluginConfig.tarballDir ?? publishPlugin.tarballDir; + // eslint-disable-next-line no-param-reassign + pluginConfig.pkgRoot = pluginConfig.pkgRoot ?? publishPlugin.pkgRoot; + // eslint-disable-next-line no-param-reassign + pluginConfig.disableScripts = pluginConfig.disableScripts ?? publishPlugin.disableScripts; + // eslint-disable-next-line no-param-reassign + pluginConfig.branches = pluginConfig.branches ?? publishPlugin.branches; + } + + await verify(pluginConfig, context); + + verified = true; +}; + +export const prepare = async (pluginConfig: PluginConfig, context: PrepareContext): Promise => { + if (!verified) { + await verify(pluginConfig, context); + } + + await prepareNpm(pluginConfig, context); + + prepared = true; +}; + +export const publish = async (pluginConfig: PluginConfig, context: PublishContext): Promise => { + const packageJson = await getPackage(pluginConfig, context); + + if (!verified) { + await verify(pluginConfig, context); + } + + if (!prepared) { + await prepareNpm(pluginConfig, context); + } + + return await publishNpm(pluginConfig, packageJson, context); +}; + +export const addChannel = async (pluginConfig: PluginConfig, context: AddChannelContext): Promise => { + if (!verified) { + await verify(pluginConfig, context); + } + + const packageJson = await getPackage(pluginConfig, context); + + return await addChannelNpm(pluginConfig, packageJson, context); +}; diff --git a/packages/semantic-release-pnpm/src/prepare.ts b/packages/semantic-release-pnpm/src/prepare.ts new file mode 100644 index 00000000..ac713ec7 --- /dev/null +++ b/packages/semantic-release-pnpm/src/prepare.ts @@ -0,0 +1,43 @@ +import { resolve } from "@visulima/path"; +import { execa } from "execa"; +import { moveFile } from "move-file"; + +import type { PrepareContext } from "./definitions/context"; +import type { PluginConfig } from "./definitions/plugin-config"; + +export default async ({ pkgRoot, tarballDir }: PluginConfig, { cwd, env, logger, nextRelease: { version }, stderr, stdout }: PrepareContext): Promise => { + const basePath = pkgRoot ? resolve(cwd, pkgRoot) : cwd; + + logger.log("Write version %s to package.json in %s", version, basePath); + + const versionResult = execa("pnpm", ["version", version, "--no-git-tag-version", "--allow-same-version"], { + cwd: basePath, + env, + preferLocal: true, + }); + + versionResult.stdout.pipe(stdout, { end: false }); + versionResult.stderr.pipe(stderr, { end: false }); + + await versionResult; + + if (tarballDir) { + logger.log("Creating npm package version %s", version); + + const packResult = execa("pnpm", ["pack", basePath], { cwd, env, preferLocal: true }); + + packResult.stdout.pipe(stdout, { end: false }); + packResult.stderr.pipe(stderr, { end: false }); + + // eslint-disable-next-line unicorn/no-await-expression-member + const tarball = (await packResult).stdout.split("\n").pop() as string; + const tarballSource = resolve(cwd, tarball); + const tarballDestination = resolve(cwd, tarballDir.trim(), tarball); + + // Only move the tarball if we need to + // Fixes: https://github.com/semantic-release/npm/issues/169 + if (tarballSource !== tarballDestination) { + await moveFile(tarballSource, tarballDestination); + } + } +}; diff --git a/packages/semantic-release-pnpm/src/publish.ts b/packages/semantic-release-pnpm/src/publish.ts new file mode 100644 index 00000000..f230fe5f --- /dev/null +++ b/packages/semantic-release-pnpm/src/publish.ts @@ -0,0 +1,73 @@ +import type { PackageJson } from "@visulima/package"; +import { resolve } from "@visulima/path"; +import AggregateError from "aggregate-error"; +import { execa } from "execa"; + +import type { PublishContext } from "./definitions/context"; +import type { PluginConfig } from "./definitions/plugin-config"; +import getChannel from "./utils/get-channel"; +import getRegistry from "./utils/get-registry"; +import type { ReleaseInfo } from "./utils/get-release-info"; +import { getReleaseInfo } from "./utils/get-release-info"; +import { reasonToNotPublish, shouldPublish } from "./utils/should-publish"; + +export default async (pluginConfig: PluginConfig, package_: PackageJson, context: PublishContext): Promise => { + const { + cwd, + env, + logger, + nextRelease: { channel, version }, + stderr, + stdout, + } = context; + const { pkgRoot, publishBranch: publishBranchConfig } = pluginConfig; + + if (shouldPublish(pluginConfig, package_)) { + const basePath = pkgRoot ? resolve(cwd, pkgRoot) : cwd; + const registry = getRegistry(package_, context); + const distributionTag = getChannel(channel); + + const { stdout: currentBranch } = await execa("git", ["rev-parse", "--abbrev-ref", "HEAD"], { + cwd, + env, + preferLocal: true, + }); + const publishBranches = typeof publishBranchConfig === "string" && publishBranchConfig.split("|"); + const isPublishBranch = publishBranches && publishBranches.includes(currentBranch); + const publishBranch = isPublishBranch ? currentBranch : "main"; + + logger.log(`Publishing version ${version} on branch ${publishBranch} to npm registry on dist-tag ${distributionTag}`); + + const pnpmArguments = ["publish", basePath, "--publish-branch", publishBranch, "--tag", distributionTag, "--registry", registry, "--no-git-checks"]; + + if (pluginConfig.disableScripts) { + pnpmArguments.push("--ignore-scripts"); + } + + const result = execa("pnpm", pnpmArguments, { + cwd, + env, + preferLocal: true, + }); + + result.stdout.pipe(stdout, { end: false }); + result.stderr.pipe(stderr, { end: false }); + + try { + await result; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + logger.log(`Failed to publish ${package_.name}@${version} to dist-tag @${distributionTag} on ${registry}: ${error.message ?? error}`); + + throw new AggregateError([error]); + } + + logger.log(`Published ${package_.name}@${version} to dist-tag @${distributionTag} on ${registry}`); + + return getReleaseInfo(package_, context, distributionTag, registry); + } + + logger.log(`Skip publishing to npm registry as ${reasonToNotPublish(pluginConfig, package_)}`); + + return false; +}; diff --git a/packages/semantic-release-pnpm/src/utils/get-channel.ts b/packages/semantic-release-pnpm/src/utils/get-channel.ts new file mode 100644 index 00000000..aafc32e6 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/get-channel.ts @@ -0,0 +1,3 @@ +import { validRange } from "semver"; + +export default (channel: string | null | undefined): string => (channel ? (validRange(channel) ? `release-${channel}` : channel) : "latest"); diff --git a/packages/semantic-release-pnpm/src/utils/get-error.ts b/packages/semantic-release-pnpm/src/utils/get-error.ts new file mode 100644 index 00000000..595107e9 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/get-error.ts @@ -0,0 +1,11 @@ +import SemanticReleaseError from "@semantic-release/error"; + +import type { ErrorContext, ErrorDefinition } from "../definitions/errors"; +import { errors } from "../definitions/errors"; + +export default (code: T, context: ErrorContext = {}): SemanticReleaseError => { + // eslint-disable-next-line security/detect-object-injection + const { details, message }: { details?: string; message: string } = (errors[code] as ErrorDefinition)(context); + + return new SemanticReleaseError(message, code, details); +}; diff --git a/packages/semantic-release-pnpm/src/utils/get-npmrc-path.ts b/packages/semantic-release-pnpm/src/utils/get-npmrc-path.ts new file mode 100644 index 00000000..77fdb4ac --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/get-npmrc-path.ts @@ -0,0 +1,38 @@ +import { ensureFileSync, isAccessibleSync } from "@visulima/fs"; +import { findCacheDirectorySync } from "@visulima/package"; +import { resolve } from "@visulima/path"; + +import getError from "./get-error"; + +const getNpmrcPath = (cwd: string, environment: NodeJS.ProcessEnv): string => { + let npmrc: string | undefined; + + const npmrcPath = resolve(cwd, ".npmrc"); + + if (environment.NPM_CONFIG_USERCONFIG && isAccessibleSync(environment.NPM_CONFIG_USERCONFIG)) { + npmrc = environment.NPM_CONFIG_USERCONFIG; + } else if (isAccessibleSync(npmrcPath)) { + npmrc = npmrcPath; + } else { + const temporaryNpmrcPath = findCacheDirectorySync("semantic-release-pnpm", { create: true, cwd }); + + if (temporaryNpmrcPath) { + ensureFileSync(temporaryNpmrcPath); + + npmrc = temporaryNpmrcPath; + } + } + + if (npmrc === undefined) { + // eslint-disable-next-line unicorn/error-message + throw new AggregateError([ + getError("ENOPNPMRC", { + npmrc: npmrcPath, + }), + ]); + } + + return npmrc; +}; + +export default getNpmrcPath; diff --git a/packages/semantic-release-pnpm/src/utils/get-pkg.ts b/packages/semantic-release-pnpm/src/utils/get-pkg.ts new file mode 100644 index 00000000..02f347c4 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/get-pkg.ts @@ -0,0 +1,31 @@ +// eslint-disable-next-line unicorn/prevent-abbreviations +import type { PackageJson } from "@visulima/package"; +import { findPackageJson } from "@visulima/package"; +import { resolve } from "@visulima/path"; +import AggregateError from "aggregate-error"; + +import type { CommonContext } from "../definitions/context"; +import getError from "./get-error"; + +interface Options { + pkgRoot?: string; +} + +export default async ({ pkgRoot }: Options, { cwd }: { cwd: CommonContext["cwd"] }): Promise => { + try { + const { packageJson } = await findPackageJson(pkgRoot ? resolve(cwd, pkgRoot) : cwd); + + if (!packageJson.name) { + throw new AggregateError([getError("ENOPKGNAME")]); + } + + return packageJson; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code === "ENOENT") { + throw new AggregateError([getError("ENOPKG")]); + } + + throw error; + } +}; diff --git a/packages/semantic-release-pnpm/src/utils/get-registry.ts b/packages/semantic-release-pnpm/src/utils/get-registry.ts new file mode 100644 index 00000000..26dc5a34 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/get-registry.ts @@ -0,0 +1,33 @@ +import { rc } from "@anolilab/rc"; +import type { PackageJson } from "@visulima/package"; +import { resolve } from "@visulima/path"; +import type { AuthOptions } from "registry-auth-token"; + +import { DEFAULT_NPM_REGISTRY } from "../definitions/constants"; +import type { CommonContext } from "../definitions/context"; + +const getRegistryUrl = (scope: string, npmrc: AuthOptions["npmrc"]): string => { + let url: string = DEFAULT_NPM_REGISTRY; + + if (npmrc) { + const registryUrl = npmrc[`${scope}:registry`] ?? npmrc.registry; + + if (registryUrl) { + url = registryUrl; + } + } + + return url.endsWith("/") ? url : `${url}/`; +}; + +export default ({ name, publishConfig: { registry } = {} }: PackageJson, { cwd, env }: CommonContext): string => + registry ?? + env.NPM_CONFIG_REGISTRY ?? + getRegistryUrl( + (name as string).split("/")[0] as string, + rc("npm", { + config: env.NPM_CONFIG_USERCONFIG ?? resolve(cwd, ".npmrc"), + cwd, + defaults: { registry: "https://registry.npmjs.org/" }, + }).config as AuthOptions["npmrc"], + ); diff --git a/packages/semantic-release-pnpm/src/utils/get-release-info.ts b/packages/semantic-release-pnpm/src/utils/get-release-info.ts new file mode 100644 index 00000000..7a14bf47 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/get-release-info.ts @@ -0,0 +1,23 @@ +import type { PackageJson } from "@visulima/package"; +import normalizeUrl from "normalize-url"; + +import type { PublishContext } from "../definitions/context"; + +export interface ReleaseInfo { + channel: string; + name: string; + url?: string; +} + +export const getReleaseInfo = ( + { name }: PackageJson, + { env: { DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/" }, nextRelease: { version } }: PublishContext, + distributionTag: string, + registry: string, +): ReleaseInfo => { + return { + channel: distributionTag, + name: `pnpm package (@${distributionTag} dist-tag)`, + url: normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY) ? `https://www.npmjs.com/package/${name}/v/${version}` : undefined, + }; +}; diff --git a/packages/semantic-release-pnpm/src/utils/nerf-dart.ts b/packages/semantic-release-pnpm/src/utils/nerf-dart.ts new file mode 100644 index 00000000..278b3da6 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/nerf-dart.ts @@ -0,0 +1,23 @@ +import { URL } from "node:url"; + +/** + * Maps a URL to an identifier. + * + * The ISC License + * Copyright (c) npm, Inc. + * + * Name courtesy schiffertronix media LLC, a New Jersey corporation + * + * @param {String} url The URL to be nerfed. + * + * @returns {String} A nerfed URL. + */ +const nerfDart = (url: string): string => { + const parsed = new URL(url); + const from = `${parsed.protocol}//${parsed.host}${parsed.pathname}`; + const real = new URL(".", from); + + return `//${real.host}${real.pathname}`; +}; + +export default nerfDart; diff --git a/packages/semantic-release-pnpm/src/utils/set-npmrc-auth.ts b/packages/semantic-release-pnpm/src/utils/set-npmrc-auth.ts new file mode 100644 index 00000000..e71a5fc0 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/set-npmrc-auth.ts @@ -0,0 +1,48 @@ +import { rc } from "@anolilab/rc"; +import { writeFile } from "@visulima/fs"; +import { resolve } from "@visulima/path"; +import AggregateError from "aggregate-error"; +import { stringify } from "ini"; +import type { AuthOptions } from "registry-auth-token"; +import getAuthToken from "registry-auth-token"; + +import { DEFAULT_NPM_REGISTRY } from "../definitions/constants"; +import type { CommonContext } from "../definitions/context"; +import getError from "./get-error"; +import nerfDart from "./nerf-dart"; + +export default async ( + npmrc: string, + registry: string, + { cwd, env: { NPM_CONFIG_USERCONFIG, NPM_EMAIL, NPM_PASSWORD, NPM_TOKEN, NPM_USERNAME }, logger }: CommonContext, +): Promise => { + logger.log("Verify authentication for registry %s", registry); + + const { config, files } = rc("npm", { + config: NPM_CONFIG_USERCONFIG ?? resolve(cwd, ".npmrc"), + cwd, + defaults: { registry: DEFAULT_NPM_REGISTRY }, + }); + + if (Array.isArray(files)) { + logger.log("Reading npm config from %s", files.join(", ")); + } + + if (getAuthToken(registry, { npmrc: config } as AuthOptions)) { + await writeFile(npmrc, stringify(config)); + + return; + } + + if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) { + await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)}\n` : ""}_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`); + + logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD, and NPM_EMAIL to ${npmrc}`); + } else if (NPM_TOKEN) { + await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)}\n` : ""}${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`); + + logger.log(`Wrote NPM_TOKEN to ${npmrc}`); + } else { + throw new AggregateError([getError("ENONPMTOKEN", { registry })]); + } +}; diff --git a/packages/semantic-release-pnpm/src/utils/should-publish.ts b/packages/semantic-release-pnpm/src/utils/should-publish.ts new file mode 100644 index 00000000..5a938bb9 --- /dev/null +++ b/packages/semantic-release-pnpm/src/utils/should-publish.ts @@ -0,0 +1,17 @@ +import type { PackageJson } from "@visulima/package"; + +import type { PluginConfig } from "../definitions/plugin-config"; + +/** + * Returns null if `npmPublish` is not `false` and `pkg.private` is not + * `true` or `pkg.workspaces` is not `undefined`. + * Returns reason otherwise. + */ +export const reasonToNotPublish = (pluginConfig: PluginConfig, package_: PackageJson): string | null => + (pluginConfig.npmPublish === false + ? "npmPublish plugin option is false" + : package_.private === true && package_.workspaces === undefined + ? "package is private and has no workspaces" + : null); + +export const shouldPublish = (pluginConfig: PluginConfig, package_: PackageJson): boolean => reasonToNotPublish(pluginConfig, package_) === null; diff --git a/packages/semantic-release-pnpm/src/verify/index.ts b/packages/semantic-release-pnpm/src/verify/index.ts new file mode 100644 index 00000000..7009419b --- /dev/null +++ b/packages/semantic-release-pnpm/src/verify/index.ts @@ -0,0 +1,46 @@ +import AggregateError from "aggregate-error"; + +import type { VerifyConditionsContext } from "../definitions/context"; +import type { PluginConfig } from "../definitions/plugin-config"; +import getNpmrcPath from "../utils/get-npmrc-path"; +import getPackage from "../utils/get-pkg"; +import { shouldPublish } from "../utils/should-publish"; +import verifyAuth from "./verify-auth"; +import verifyConfig from "./verify-config"; +import verifyPnpm from "./verify-pnpm"; + +const verify = async (pluginConfig: PluginConfig, context: VerifyConditionsContext): Promise => { + let errors: Error[] = verifyConfig(pluginConfig); + + try { + await verifyPnpm(context); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typedError = error as AggregateError; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + errors = [...errors, ...(typedError.errors ?? [error])]; + } + + try { + const packageJson = await getPackage(pluginConfig, context); + + if (shouldPublish(pluginConfig, packageJson)) { + const npmrc = getNpmrcPath(context.cwd, context.env); + + await verifyAuth(npmrc, packageJson, context); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const typedError = error as AggregateError; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + errors = [...errors, ...(typedError.errors ?? [error])]; + } + + if (errors.length > 0) { + throw new AggregateError(errors); + } +}; + +export default verify; diff --git a/packages/semantic-release-pnpm/src/verify/verify-auth.ts b/packages/semantic-release-pnpm/src/verify/verify-auth.ts new file mode 100644 index 00000000..f67964c5 --- /dev/null +++ b/packages/semantic-release-pnpm/src/verify/verify-auth.ts @@ -0,0 +1,43 @@ +import type { PackageJson } from "@visulima/package"; +import AggregateError from "aggregate-error"; +import { execa } from "execa"; +import normalizeUrl from "normalize-url"; + +import type { CommonContext } from "../definitions/context"; +import getError from "../utils/get-error"; +import getRegistry from "../utils/get-registry"; +import setNpmrcAuth from "../utils/set-npmrc-auth"; + +export default async (npmrc: string, package_: PackageJson, context: CommonContext): Promise => { + const { + cwd, + env: { DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/", ...environment }, + logger, + stderr, + stdout, + } = context; + const registry = getRegistry(package_, context); + + await setNpmrcAuth(npmrc, registry, context); + + if (normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY)) { + try { + logger.log(`Running "pnpm whoami" to verify authentication on registry "${registry}"`); + + const whoamiResult = execa("pnpm", ["whoami", "--userconfig", npmrc, "--registry", registry], { + cwd, + env: environment, + preferLocal: true, + }); + + whoamiResult.stdout.pipe(stdout, { end: false }); + whoamiResult.stderr.pipe(stderr, { end: false }); + + await whoamiResult; + } catch { + throw new AggregateError([getError("EINVALIDNPMTOKEN", { registry })]); + } + } else { + logger.log(`Skipping authentication verification for non-default registry "${registry}"`); + } +}; diff --git a/packages/semantic-release-pnpm/src/verify/verify-config.ts b/packages/semantic-release-pnpm/src/verify/verify-config.ts new file mode 100644 index 00000000..170438d2 --- /dev/null +++ b/packages/semantic-release-pnpm/src/verify/verify-config.ts @@ -0,0 +1,51 @@ +import type SemanticReleaseError from "@semantic-release/error"; + +import type { PluginConfig } from "../definitions/plugin-config"; +import getError from "../utils/get-error"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const isString = (value: any): boolean => typeof value === "string"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const isNil = (value: any): boolean => value === null || value === undefined; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const isNonEmptyString = (value: any): boolean => { + if (!isString(value)) { + return false; + } + + return (value as string).trim() !== ""; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ValidatorFunction = (value: any) => boolean; + +const VALIDATORS: Record = { + branches: Array.isArray, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + npmPublish: (value: any): boolean => typeof value === "boolean", + pkgRoot: isNonEmptyString, + publishBranch: isNonEmptyString, + tarballDir: isNonEmptyString, +}; + +export default (config: PluginConfig): SemanticReleaseError[] => + // eslint-disable-next-line unicorn/no-array-reduce + Object.entries(config).reduce((errors, [option, value]) => { + if (isNil(value)) { + return errors; + } + + if (!(option in VALIDATORS)) { + return errors; + } + + // eslint-disable-next-line security/detect-object-injection + if (VALIDATORS[option]?.(value)) { + return errors; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return [...errors, getError(`EINVALID${option.toUpperCase()}` as any, { [option]: value })]; + }, []); diff --git a/packages/semantic-release-pnpm/src/verify/verify-pnpm.ts b/packages/semantic-release-pnpm/src/verify/verify-pnpm.ts new file mode 100644 index 00000000..4ec9cfa0 --- /dev/null +++ b/packages/semantic-release-pnpm/src/verify/verify-pnpm.ts @@ -0,0 +1,22 @@ +import { getPackageManagerVersion } from "@visulima/package"; +import AggregateError from "aggregate-error"; +import { gte } from "semver"; + +import type { CommonContext } from "../definitions/context"; +import getError from "../utils/get-error"; + +const MIN_PNPM_VERSION = "8.0.0"; + +export default async function verifyPnpm({ logger }: CommonContext): Promise { + logger.log(`Verify pnpm version is >= ${MIN_PNPM_VERSION}`); + + const version = getPackageManagerVersion("pnpm"); + + if (version !== "pnpm") { + throw new AggregateError([new Error("pnpm is not installed")]); + } + + if (gte(MIN_PNPM_VERSION, version)) { + throw new AggregateError([getError("EINVALIDPNPM", { version: String(version) })]); + } +} diff --git a/packages/semantic-release-pnpm/tsconfig.eslint.json b/packages/semantic-release-pnpm/tsconfig.eslint.json new file mode 100644 index 00000000..c63f963b --- /dev/null +++ b/packages/semantic-release-pnpm/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": ["__tests__/**/*", "src/**/*", "*.d.ts", "tsup.config.ts"] +} diff --git a/packages/semantic-release-pnpm/tsconfig.json b/packages/semantic-release-pnpm/tsconfig.json new file mode 100644 index 00000000..f0ee80dc --- /dev/null +++ b/packages/semantic-release-pnpm/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "include": ["src/**/*", "*.d.ts"], + "compilerOptions": { + "moduleResolution": "bundler" + } +} diff --git a/packages/semantic-release-pnpm/tsup.config.ts b/packages/semantic-release-pnpm/tsup.config.ts new file mode 100644 index 00000000..5fdb8580 --- /dev/null +++ b/packages/semantic-release-pnpm/tsup.config.ts @@ -0,0 +1,25 @@ +import type { Options } from "tsup"; +import { defineConfig } from "tsup"; + +// @ts-ignore +export default defineConfig((options: Options) => { + return { + ...options, + treeshake: true, + // react external https://github.com/vercel/turborepo/issues/360#issuecomment-1013885148 + external: ["semantic-release"], + silent: !options.watch, + minify: process.env["NODE_ENV"] === "production", + minifyWhitespace: process.env["NODE_ENV"] === "production", + incremental: !options.watch, + dts: true, + sourcemap: true, + clean: true, + splitting: true, + shims: true, + target: ["es2022", "node18"], + declaration: true, + entry: ["src/index.ts"], + format: ["esm"], + }; +}); diff --git a/packages/semantic-release-pnpm/vitest.config.ts b/packages/semantic-release-pnpm/vitest.config.ts new file mode 100644 index 00000000..59d88c72 --- /dev/null +++ b/packages/semantic-release-pnpm/vitest.config.ts @@ -0,0 +1,5 @@ +import { getVitestConfig } from "../../tools/get-vitest-config"; + +const config = getVitestConfig(); + +export default config; diff --git a/plop/package/package.json.hbs b/plop/package/package.json.hbs index b0e66b32..3fd6284e 100644 --- a/plop/package/package.json.hbs +++ b/plop/package/package.json.hbs @@ -43,7 +43,9 @@ "source": "src/index.ts", "types": "dist/index.d.ts", "files": [ - "dist" + "dist", + "README.md", + "CHANGELOG.md" ], "repository": { "type": "git", @@ -83,7 +85,6 @@ "@babel/core": "^7.24.3", "@rushstack/eslint-plugin-security": "^0.8.1", "@secretlint/secretlint-rule-preset-recommend": "^8.1.2", - "@types/micromatch": "^4.0.6", "@types/node": "18.18.14", "@vitest/coverage-v8": "^1.4.0", "@vitest/ui": "^1.4.0", @@ -115,5 +116,5 @@ "info_on_disabling_jsonc_sort_keys_rule": false, "info_on_disabling_etc_no_deprecated": false } - }, + } } diff --git a/plop/package/tsconfig.dev.json.hbs b/plop/package/tsconfig.dev.json.hbs deleted file mode 100644 index 555c43f1..00000000 --- a/plop/package/tsconfig.dev.json.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "jsx": "react-jsxdev" - } -} diff --git a/plop/package/tsconfig.eslint.json.hbs b/plop/package/tsconfig.eslint.json.hbs index 36db3054..39eaefbf 100644 --- a/plop/package/tsconfig.eslint.json.hbs +++ b/plop/package/tsconfig.eslint.json.hbs @@ -4,5 +4,5 @@ "compilerOptions": { "baseUrl": "." }, - "include": ["__docs__/**/*", "__tests__/**/*", "src/**/*", "*.d.ts", "build.config.ts", "tsup.config.ts"] + "include": ["__docs__/**/*", "__tests__/**/*", "src/**/*", "*.d.ts", "tsup.config.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1a6d251..957599a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,7 +154,7 @@ importers: devDependencies: '@anolilab/eslint-config': specifier: ^15.0.3 - version: 15.0.3(@babel/core@7.24.5)(eslint-plugin-i@2.29.1(eslint@8.57.0))(eslint-plugin-you-dont-need-lodash-underscore@6.14.0)(eslint@8.57.0)(typescript@5.4.5) + version: 15.0.3(@babel/core@7.24.5)(eslint-plugin-editorconfig@4.0.3)(eslint-plugin-i@2.29.1(eslint@8.57.0))(eslint-plugin-you-dont-need-lodash-underscore@6.14.0)(eslint@8.57.0)(typescript@5.4.5) '@anolilab/prettier-config': specifier: ^5.0.14 version: 5.0.14(prettier@3.2.5) @@ -237,6 +237,266 @@ importers: specifier: ^1.6.0 version: 1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0) + packages/rc: + dependencies: + '@visulima/fs': + specifier: ^2.1.1 + version: 2.1.1 + ini: + specifier: ^4.1.3 + version: 4.1.3 + ts-deepmerge: + specifier: ^7.0.0 + version: 7.0.0 + devDependencies: + '@anolilab/eslint-config': + specifier: ^15.0.3 + version: 15.0.3(@babel/core@7.24.5)(eslint-plugin-editorconfig@4.0.3)(eslint-plugin-i@2.29.1(eslint@8.57.0))(eslint-plugin-you-dont-need-lodash-underscore@6.14.0)(eslint@8.57.0)(typescript@5.4.5) + '@anolilab/prettier-config': + specifier: ^5.0.14 + version: 5.0.14(prettier@3.2.5) + '@anolilab/semantic-release-preset': + specifier: ^8.0.3 + version: 8.0.3(semantic-release@23.1.1(typescript@5.4.5)) + '@babel/core': + specifier: ^7.24.3 + version: 7.24.5 + '@rushstack/eslint-plugin-security': + specifier: ^0.8.1 + version: 0.8.1(eslint@8.57.0)(typescript@5.4.5) + '@secretlint/secretlint-rule-preset-recommend': + specifier: ^8.1.2 + version: 8.2.4 + '@types/ini': + specifier: ^4.1.0 + version: 4.1.0 + '@types/node': + specifier: 18.18.14 + version: 18.18.14 + '@visulima/path': + specifier: ^1.0.0 + version: 1.0.0 + '@vitest/coverage-v8': + specifier: ^1.4.0 + version: 1.6.0(vitest@1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0)) + '@vitest/ui': + specifier: ^1.4.0 + version: 1.6.0(vitest@1.6.0) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-deprecation: + specifier: ^2.0.0 + version: 2.0.0(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-etc: + specifier: ^2.0.3 + version: 2.0.3(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-import: + specifier: npm:eslint-plugin-i@^2.29.1 + version: eslint-plugin-i@2.29.1(eslint@8.57.0) + eslint-plugin-mdx: + specifier: ^3.1.5 + version: 3.1.5(eslint@8.57.0) + eslint-plugin-vitest: + specifier: ^0.4.1 + version: 0.4.1(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0)) + eslint-plugin-vitest-globals: + specifier: ^1.5.0 + version: 1.5.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + rimraf: + specifier: ^5.0.5 + version: 5.0.7 + secretlint: + specifier: 8.1.2 + version: 8.1.2 + semantic-release: + specifier: ^23.0.5 + version: 23.1.1(typescript@5.4.5) + sort-package-json: + specifier: ^2.8.0 + version: 2.10.0 + tempy: + specifier: ^3.1.0 + version: 3.1.0 + tsup: + specifier: ^8.0.2 + version: 8.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.18.14)(typescript@5.4.5))(typescript@5.4.5) + typescript: + specifier: ^5.4.3 + version: 5.4.5 + vitest: + specifier: ^1.4.0 + version: 1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0) + + packages/semantic-release-pnpm: + dependencies: + '@anolilab/rc': + specifier: workspace:* + version: link:../rc + '@semantic-release/error': + specifier: ^4.0.0 + version: 4.0.0 + '@visulima/fs': + specifier: ^2.1.1 + version: 2.1.1 + '@visulima/package': + specifier: 1.8.1 + version: 1.8.1 + '@visulima/path': + specifier: ^1.0.0 + version: 1.0.0 + aggregate-error: + specifier: ^5.0.0 + version: 5.0.0 + execa: + specifier: ^9.1.0 + version: 9.1.0 + ini: + specifier: 4.1.2 + version: 4.1.2 + move-file: + specifier: ^3.1.0 + version: 3.1.0 + normalize-url: + specifier: ^8.0.1 + version: 8.0.1 + registry-auth-token: + specifier: ^5.0.2 + version: 5.0.2 + semver: + specifier: ^7.6.2 + version: 7.6.2 + devDependencies: + '@anolilab/eslint-config': + specifier: ^15.0.3 + version: 15.0.3(@babel/core@7.24.5)(eslint-plugin-editorconfig@4.0.3)(eslint-plugin-i@2.29.1(eslint@8.57.0))(eslint-plugin-you-dont-need-lodash-underscore@6.14.0)(eslint@8.57.0)(typescript@5.4.5) + '@anolilab/prettier-config': + specifier: ^5.0.14 + version: 5.0.14(prettier@3.2.5) + '@anolilab/semantic-release-preset': + specifier: ^8.0.3 + version: 8.0.3(semantic-release@23.1.1(typescript@5.4.5)) + '@babel/core': + specifier: ^7.24.3 + version: 7.24.5 + '@rushstack/eslint-plugin-security': + specifier: ^0.8.1 + version: 0.8.1(eslint@8.57.0)(typescript@5.4.5) + '@secretlint/secretlint-rule-preset-recommend': + specifier: ^8.1.2 + version: 8.2.4 + '@semantic-release/changelog': + specifier: ^6.0.3 + version: 6.0.3(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/git': + specifier: ^10.0.1 + version: 10.0.1(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/github': + specifier: ^10.0.4 + version: 10.0.5(semantic-release@23.1.1(typescript@5.4.5)) + '@types/dockerode': + specifier: ^3.3.29 + version: 3.3.29 + '@types/ini': + specifier: ^4.1.0 + version: 4.1.0 + '@types/node': + specifier: 18.18.14 + version: 18.18.14 + '@types/semantic-release__error': + specifier: 3.0.3 + version: 3.0.3 + '@types/semver': + specifier: 7.5.8 + version: 7.5.8 + '@types/stream-buffers': + specifier: ^3.0.7 + version: 3.0.7 + '@vitest/coverage-v8': + specifier: ^1.4.0 + version: 1.6.0(vitest@1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0)) + '@vitest/ui': + specifier: ^1.4.0 + version: 1.6.0(vitest@1.6.0) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + dockerode: + specifier: 4.0.2 + version: 4.0.2 + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-deprecation: + specifier: ^2.0.0 + version: 2.0.0(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-editorconfig: + specifier: ^4.0.3 + version: 4.0.3 + eslint-plugin-import: + specifier: npm:eslint-plugin-i@2.29.1 + version: eslint-plugin-i@2.29.1(eslint@8.57.0) + eslint-plugin-mdx: + specifier: ^3.1.5 + version: 3.1.5(eslint@8.57.0) + eslint-plugin-n: + specifier: ^17.7.0 + version: 17.7.0(eslint@8.57.0) + eslint-plugin-vitest: + specifier: ^0.4.1 + version: 0.4.1(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0)) + eslint-plugin-vitest-globals: + specifier: ^1.5.0 + version: 1.5.0 + eslint-plugin-you-dont-need-lodash-underscore: + specifier: ^6.14.0 + version: 6.14.0 + get-stream: + specifier: 9.0.1 + version: 9.0.1 + got: + specifier: ^14.2.1 + version: 14.3.0 + p-retry: + specifier: ^6.2.0 + version: 6.2.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + rimraf: + specifier: ^5.0.5 + version: 5.0.7 + secretlint: + specifier: 8.1.2 + version: 8.1.2 + semantic-release: + specifier: ^23.0.5 + version: 23.1.1(typescript@5.4.5) + sort-package-json: + specifier: ^2.8.0 + version: 2.10.0 + stream-buffers: + specifier: ^3.0.2 + version: 3.0.2 + tempy: + specifier: ^3.1.0 + version: 3.1.0 + tsup: + specifier: ^8.0.2 + version: 8.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.18.14)(typescript@5.4.5))(typescript@5.4.5) + typescript: + specifier: ^5.4.3 + version: 5.4.5 + vitest: + specifier: ^1.4.0 + version: 1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0) + packages: '@aashutoshrathi/word-wrap@1.2.6': @@ -396,6 +656,12 @@ packages: peerDependencies: semantic-release: ^18.0.1 + '@anolilab/semantic-release-preset@8.0.3': + resolution: {integrity: sha512-/8ddc+6ILuL8VmxcJ6+9aGwGj/LL8CkWwbuXQcvCIjRPdSXk5anuCl2It8STa70UKBZvkvwIsttlRJzjxAqiCg==} + engines: {node: ^18.17 || >=20.6.1} + peerDependencies: + semantic-release: '>=22' + '@anolilab/textlint-config@8.0.16': resolution: {integrity: sha512-kzIOKha8sIPo4kOMCq2lcflFH4PSODEI7+TQpKcp70EmUQkOVd7+Yw1laltXKs2ItDDD4RHYKaroRz0MmnuOtg==} engines: {node: '>=18.* <=21.*'} @@ -519,6 +785,9 @@ packages: resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} engines: {node: '>=6.9.0'} + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -740,138 +1009,276 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.20.2': resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.20.2': resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.20.2': resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.20.2': resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.20.2': resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.20.2': resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.20.2': resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.20.2': resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.20.2': resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.20.2': resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.20.2': resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.20.2': resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.20.2': resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.20.2': resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.20.2': resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.20.2': resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.20.2': resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.20.2': resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.20.2': resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.20.2': resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.20.2': resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.20.2': resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.20.2': resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} @@ -1083,6 +1490,10 @@ packages: resolution: {integrity: sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==} engines: {node: '>= 14'} + '@octokit/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + '@octokit/auth-token@5.1.1': resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} engines: {node: '>= 18'} @@ -1091,6 +1502,10 @@ packages: resolution: {integrity: sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==} engines: {node: '>= 14'} + '@octokit/core@5.2.0': + resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + engines: {node: '>= 18'} + '@octokit/core@6.1.2': resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} engines: {node: '>= 18'} @@ -1103,10 +1518,18 @@ packages: resolution: {integrity: sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==} engines: {node: '>= 14'} + '@octokit/endpoint@9.0.5': + resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} + engines: {node: '>= 18'} + '@octokit/graphql@5.0.6': resolution: {integrity: sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==} engines: {node: '>= 14'} + '@octokit/graphql@7.1.0': + resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + engines: {node: '>= 18'} + '@octokit/graphql@8.1.1': resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} engines: {node: '>= 18'} @@ -1114,6 +1537,9 @@ packages: '@octokit/openapi-types@18.1.1': resolution: {integrity: sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==} + '@octokit/openapi-types@20.0.0': + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + '@octokit/openapi-types@22.2.0': resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} @@ -1129,12 +1555,24 @@ packages: peerDependencies: '@octokit/core': '>=4' + '@octokit/plugin-paginate-rest@9.2.1': + resolution: {integrity: sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + '@octokit/plugin-retry@4.1.6': resolution: {integrity: sha512-obkYzIgEC75r8+9Pnfiiqy3y/x1bc3QLE5B7qvv9wi9Kj0R5tGQFC6QMBg1154WQ9lAVypuQDGyp3hNpp15gQQ==} engines: {node: '>= 14'} peerDependencies: '@octokit/core': '>=3' + '@octokit/plugin-retry@6.0.1': + resolution: {integrity: sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + '@octokit/plugin-retry@7.1.1': resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} engines: {node: '>= 18'} @@ -1147,6 +1585,12 @@ packages: peerDependencies: '@octokit/core': ^4.0.0 + '@octokit/plugin-throttling@8.2.0': + resolution: {integrity: sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^5.0.0 + '@octokit/plugin-throttling@9.3.0': resolution: {integrity: sha512-B5YTToSRTzNSeEyssnrT7WwGhpIdbpV9NKIs3KyTWHX6PhpYn7gqF/+lL3BvsASBM3Sg5BAUYk7KZx5p/Ec77w==} engines: {node: '>= 18'} @@ -1157,6 +1601,10 @@ packages: resolution: {integrity: sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==} engines: {node: '>= 14'} + '@octokit/request-error@5.1.0': + resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} + engines: {node: '>= 18'} + '@octokit/request-error@6.1.1': resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} engines: {node: '>= 18'} @@ -1165,6 +1613,10 @@ packages: resolution: {integrity: sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==} engines: {node: '>= 14'} + '@octokit/request@8.4.0': + resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} + engines: {node: '>= 18'} + '@octokit/request@9.1.1': resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} engines: {node: '>= 18'} @@ -1172,12 +1624,18 @@ packages: '@octokit/tsconfig@1.0.2': resolution: {integrity: sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==} + '@octokit/types@12.6.0': + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + '@octokit/types@13.5.0': resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} '@octokit/types@9.3.2': resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@phenomnomnominal/tsquery@5.0.1': resolution: {integrity: sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==} peerDependencies: @@ -1348,6 +1806,12 @@ packages: peerDependencies: semantic-release: '>=18.0.0' + '@semantic-release/commit-analyzer@11.1.0': + resolution: {integrity: sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==} + engines: {node: ^18.17 || >=20.6.1} + peerDependencies: + semantic-release: '>=20.1.0' + '@semantic-release/commit-analyzer@12.0.0': resolution: {integrity: sha512-qG+md5gdes+xa8zP7lIo1fWE17zRdO8yMCaxh9lyL65TQleoSv8WHHOqRURfghTytUh+NpkSyBprQ5hrkxOKVQ==} engines: {node: '>=20.8.1'} @@ -1386,12 +1850,30 @@ packages: peerDependencies: semantic-release: '>=20.1.0' + '@semantic-release/github@10.0.5': + resolution: {integrity: sha512-hmuCDkfru/Uc9+ZBNOSremAupu6BCslvOVDiG0wYcL8TQodCycp6uvwDyeym1H0M4l3ob9c0s0xMBiZjjXQ2yA==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=20.1.0' + '@semantic-release/github@8.1.0': resolution: {integrity: sha512-erR9E5rpdsz0dW1I7785JtndQuMWN/iDcemcptf67tBNOmBUN0b2YNOgcjYUnBpgRpZ5ozfBHrK7Bz+2ets/Dg==} engines: {node: '>=14.17'} peerDependencies: semantic-release: '>=18.0.0-beta.1' + '@semantic-release/github@9.2.6': + resolution: {integrity: sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==} + engines: {node: '>=18'} + peerDependencies: + semantic-release: '>=20.1.0' + + '@semantic-release/npm@11.0.3': + resolution: {integrity: sha512-KUsozQGhRBAnoVg4UMZj9ep436VEGwT536/jwSqB7vcEfA6oncCUU7UIYTRdLx7GvTtqn0kBjnkfLVkcnBa2YQ==} + engines: {node: ^18.17 || >=20} + peerDependencies: + semantic-release: '>=20.1.0' + '@semantic-release/npm@12.0.1': resolution: {integrity: sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==} engines: {node: '>=20.8.1'} @@ -1410,6 +1892,12 @@ packages: peerDependencies: semantic-release: '>=18.0.0-beta.1' + '@semantic-release/release-notes-generator@12.1.0': + resolution: {integrity: sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==} + engines: {node: ^18.17 || >=20.6.1} + peerDependencies: + semantic-release: '>=20.1.0' + '@semantic-release/release-notes-generator@13.0.0': resolution: {integrity: sha512-LEeZWb340keMYuREMyxrODPXJJ0JOL8D/mCl74B4LdzbxhtXV2LrPN2QBEcGJrlQhoqLO0RhxQb6masHytKw+A==} engines: {node: '>=20.8.1'} @@ -1430,6 +1918,10 @@ packages: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} + '@sindresorhus/is@6.3.1': + resolution: {integrity: sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==} + engines: {node: '>=16'} + '@sindresorhus/merge-streams@1.0.0': resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} engines: {node: '>=18'} @@ -1641,6 +2133,12 @@ packages: '@types/debug@4.1.8': resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@3.3.29': + resolution: {integrity: sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==} + '@types/estree-jsx@1.0.0': resolution: {integrity: sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ==} @@ -1659,6 +2157,12 @@ packages: '@types/http-cache-semantics@4.0.2': resolution: {integrity: sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==} + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/ini@4.1.0': + resolution: {integrity: sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==} + '@types/inquirer@9.0.7': resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==} @@ -1698,9 +2202,21 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/retry@0.12.2': + resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} + + '@types/semantic-release__error@3.0.3': + resolution: {integrity: sha512-zFa+QAM80q65lQc5NVnKR5BgR0ChuLnBkfRBwa9xRqzAHOJx+mozSV2g1d489mKRqYezqxYC7XI0rEVoyF1f2w==} + '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/ssh2@1.15.0': + resolution: {integrity: sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==} + + '@types/stream-buffers@3.0.7': + resolution: {integrity: sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==} + '@types/supports-color@8.1.1': resolution: {integrity: sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==} @@ -1867,6 +2383,25 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@visulima/fs@2.1.1': + resolution: {integrity: sha512-KBcdscZZyZOQRdem0pLSmAT0r85ZmckUD61XRlgnyrjTOy9dUJ/gMoosa5UvcFEBTV89qOKpOTYynZFb+9Hn5g==} + engines: {node: '>=18.* <=21.*'} + os: [darwin, linux, win32] + peerDependencies: + yaml: ^2.4.0 + peerDependenciesMeta: + yaml: + optional: true + + '@visulima/package@1.8.1': + resolution: {integrity: sha512-ksrIqIKeZn1ba2AOrPx5ipYtCRwOXhMBHCS0BQWjTrmlcaZgiBqrw9Bx7EbyJflqedOesIbQTTWAaGw8G86rog==} + engines: {node: '>=18.* <=21.*'} + os: [darwin, linux, win32] + + '@visulima/path@1.0.0': + resolution: {integrity: sha512-9bJY01zRStrPqk2yKItIThZ+bRvPLHEBtvHUIGPgkmDF5fydF81IBJUxj558ZsQ8PVTcF/uMblHvdNVxyCjclQ==} + engines: {node: '>=18.* <=21.*'} + '@vitest/coverage-v8@1.6.0': resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==} peerDependencies: @@ -2002,6 +2537,10 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -2047,6 +2586,9 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -2095,6 +2637,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} @@ -2105,6 +2650,10 @@ packages: resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} engines: {node: '>=0.6'} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + binaryextensions@4.18.0: resolution: {integrity: sha512-PQu3Kyv9dM4FnwB7XGj1+HucW+ShvJzJqjuw1JkKVs1mWdwOKVcRjOi+pV9X52A0tNvrPCsPkbFFQb+wE1EAXw==} engines: {node: '>=0.8'} @@ -2166,6 +2715,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buildcheck@0.0.6: + resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==} + engines: {node: '>=10.0.0'} + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -2177,6 +2730,12 @@ packages: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} + bundle-require@4.1.0: + resolution: {integrity: sha512-FeArRFM+ziGkRViKRnSTbHZc35dgmR9yNog05Kn0+ItI59pOAISGvnnIwW1WgFZQW59IxD9QpJnUPkdIPfZuXg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2189,6 +2748,10 @@ packages: resolution: {integrity: sha512-3SD4rrMu1msNGEtNSt8Od6enwdo//U9s4ykmXfA2TD58kcLkCobtCDiby7kNyj7a/Q7lz/mAesAFI54rTdnvBA==} engines: {node: '>=14.16'} + cacheable-request@12.0.1: + resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==} + engines: {node: '>=18'} + cachedir@2.3.0: resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} engines: {node: '>=6'} @@ -2291,6 +2854,13 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chrono-node@2.7.5: resolution: {integrity: sha512-VJWqFN5rWmXVvXAxOD4i0jX8Tb4cLswaslyaAFhxM45zNXPsZleygPbgiaYBD7ORb9fj07zBgJb0Q6eKL+0iJg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2413,6 +2983,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -2420,6 +2994,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + comment-parser@1.4.0: resolution: {integrity: sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw==} engines: {node: '>= 12.0.0'} @@ -2551,6 +3129,10 @@ packages: typescript: optional: true + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -2764,6 +3346,14 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + docker-modem@5.0.3: + resolution: {integrity: sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==} + engines: {node: '>= 8.0'} + + dockerode@4.0.2: + resolution: {integrity: sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==} + engines: {node: '>= 8.0'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -2819,6 +3409,11 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -2907,6 +3502,11 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.20.2: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -3018,6 +3618,10 @@ packages: eslint: ^7.0.0 || ^8.0.0 typescript: ^4.2.4 || ^5.0.0 + eslint-plugin-editorconfig@4.0.3: + resolution: {integrity: sha512-5YeDxm6mlv75DrTbRBK9Jw2ogqhjiz8ZCvv9bkuz/MXq0603q9FpQvQlamtas4bX1Gji4YcksY7dq7stPeGaLQ==} + engines: {node: '>=14', npm: '>=8'} + eslint-plugin-es-x@7.6.0: resolution: {integrity: sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3463,6 +4067,10 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} + form-data-encoder@4.0.2: + resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==} + engines: {node: '>= 18'} + form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -3690,6 +4298,10 @@ packages: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} + got@14.3.0: + resolution: {integrity: sha512-vZkrXdq5BtPWTXqvjXSpl6zky3zpHaOVfSug/RfFHu3YrtSsvYzopVMDqrh2do77WnGoCSSRCHW25zXOSAQ9zw==} + engines: {node: '>=20'} + graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -3832,6 +4444,10 @@ packages: resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} engines: {node: '>=10.19.0'} + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} + https-proxy-agent@7.0.2: resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} engines: {node: '>= 14'} @@ -3934,6 +4550,10 @@ packages: resolution: {integrity: sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inquirer@8.2.5: resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} engines: {node: '>=12.0.0'} @@ -3993,6 +4613,10 @@ packages: is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} @@ -4115,6 +4739,10 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-network-error@1.1.0: + resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==} + engines: {node: '>=16'} + is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4346,6 +4974,10 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4419,6 +5051,9 @@ packages: jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -4435,6 +5070,9 @@ packages: keyv@4.5.3: resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -4447,6 +5085,10 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + latest-version@7.0.0: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} @@ -4497,6 +5139,10 @@ packages: load-plugin@6.0.3: resolution: {integrity: sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -5117,6 +5763,10 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -5146,6 +5796,9 @@ packages: misspellings@1.1.0: resolution: {integrity: sha512-4QT2u/8X7PccbiHUcsZeEZrt3jGIVEpfcQ1RU01wDHKHVNtNhaP+0Xmsg7YPxD7OCc8bO802BTEWeGPvAXBwuw==} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -5165,6 +5818,10 @@ packages: moment@2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} + move-file@3.1.0: + resolution: {integrity: sha512-4aE3U7CCBWgrQlQDMq8da4woBWDGHioJFiOZ8Ie6Yq2uwYQ9V2kGhTz4x3u6Wc+OU17nw0yc3rJ/lQ4jIiPe3A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -5189,6 +5846,9 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nan@2.19.0: + resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -5277,6 +5937,14 @@ packages: resolution: {integrity: sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==} engines: {node: ^16.14.0 || >=18.0.0} + normalize-package-data@6.0.1: + resolution: {integrity: sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -5480,6 +6148,10 @@ packages: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} + p-cancelable@4.0.1: + resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} + engines: {node: '>=14.16'} + p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} @@ -5576,6 +6248,10 @@ packages: resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} engines: {node: '>=12'} + p-retry@6.2.0: + resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} + engines: {node: '>=16.17'} + p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} @@ -5736,6 +6412,9 @@ packages: pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} @@ -5773,6 +6452,10 @@ packages: resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} engines: {node: '>=0.10.0'} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + pkg-conf@2.1.0: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} engines: {node: '>=4'} @@ -5796,6 +6479,18 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + postcss@8.4.38: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} @@ -5864,6 +6559,9 @@ packages: pump@1.0.3: resolution: {integrity: sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==} + pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -5966,6 +6664,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + readline-transform@1.0.0: resolution: {integrity: sha512-7KA6+N9IGat52d83dvxnApAWN+MtVb1MiVuMR/cf1O4kYsJG+g/Aav0AHcHKsb6StinayfPLne0+fMX2sOzAKg==} engines: {node: '>=6'} @@ -6140,6 +6842,10 @@ packages: retext-profanities@7.2.2: resolution: {integrity: sha512-nwrR987v3m7+JQ8wyK8oE+adqS1aYUyHyf+k6omflI/8PL9Slbp/39YieTJJvrmR0udBe2iV7aURXW5/3Uj12w==} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -6214,6 +6920,11 @@ packages: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} engines: {node: ^14.0.0 || >=16.0.0} + secretlint@8.1.2: + resolution: {integrity: sha512-YJf1WoIu+bevQdAwc1YnK9KdytOJ+5g9lRa90nmTyC2WlhIBK7s6CEVw46kUzxn4y+UVEF9POn7vDsyThqrQQQ==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + secretlint@8.2.3: resolution: {integrity: sha512-B+Qqb1GwidwDUOryXWtGO+p4FfgvzyaVApPnq7eBBggyGhb3UTSfqX4Tupn2tMaq8gowLZrY8xYixHiPvpfeVw==} engines: {node: ^14.13.1 || >=16.0.0} @@ -6369,6 +7080,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -6391,6 +7106,9 @@ packages: spdx-license-ids@3.0.15: resolution: {integrity: sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==} + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + split-transform-stream@0.1.1: resolution: {integrity: sha512-nV8lOb9BKS3BqODBjmzELm0Kl878nWoTjdfn6z/v6d/zW8YS/EQ76fP11a/D6Fm6QTsbLdsFJBIpz6t17zHJnQ==} @@ -6413,6 +7131,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + ssh2@1.15.0: + resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==} + engines: {node: '>=10.16.0'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -6541,6 +7263,11 @@ packages: structured-source@4.0.0: resolution: {integrity: sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + super-regex@1.0.0: resolution: {integrity: sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==} engines: {node: '>=18'} @@ -6589,6 +7316,9 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-fs@2.0.1: + resolution: {integrity: sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -6799,10 +7529,17 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + traverse@0.6.9: resolution: {integrity: sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==} engines: {node: '>= 0.4'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -6832,6 +7569,13 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-deepmerge@7.0.0: + resolution: {integrity: sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==} + engines: {node: '>=14.13.1'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -6856,6 +7600,25 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tsup@8.0.2: + resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + tsutils-etc@1.4.2: resolution: {integrity: sha512-2Dn5SxTDOu6YWDNKcx1xu2YUy6PUeKrWZB/x2cQ8vY2+iz3JRembKn/iZ0JLT1ZudGNwQQvtFX9AwvRHbXuPUg==} hasBin: true @@ -6869,6 +7632,9 @@ packages: 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' + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + txt-ast-traverse@1.2.1: resolution: {integrity: sha512-rxih9n0dRqIiVSKlm4Rkdz4QH4f8RvX6rZiDVXgm3Je8vpvJ4LOUXxoRoBVXNkIVpA1x5wBlNDXtdLzpaxzFIg==} deprecated: See https://github.com/textlint/textlint/issues/455 @@ -6917,6 +7683,10 @@ packages: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} + type-fest@4.18.2: + resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==} + engines: {node: '>=16'} + type-fest@4.8.3: resolution: {integrity: sha512-//BaTm14Q/gHBn09xlnKNqfI8t6bmdzx2DXYfPBNofN0WUybCEUDcbCWcTa0oF09lzLjZgPphXAsvRiMK0V6Bw==} engines: {node: '>=16'} @@ -7288,9 +8058,15 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -7448,7 +8224,7 @@ snapshots: - '@types/node' - typescript - '@anolilab/eslint-config@15.0.3(@babel/core@7.24.5)(eslint-plugin-i@2.29.1(eslint@8.57.0))(eslint-plugin-you-dont-need-lodash-underscore@6.14.0)(eslint@8.57.0)(typescript@5.4.5)': + '@anolilab/eslint-config@15.0.3(@babel/core@7.24.5)(eslint-plugin-editorconfig@4.0.3)(eslint-plugin-i@2.29.1(eslint@8.57.0))(eslint-plugin-you-dont-need-lodash-underscore@6.14.0)(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@anolilab/package-json-utils': 3.0.9 '@babel/core': 7.24.5 @@ -7498,6 +8274,7 @@ snapshots: toml-eslint-parser: 0.6.0 yaml-eslint-parser: 1.2.2 optionalDependencies: + eslint-plugin-editorconfig: 4.0.3 eslint-plugin-you-dont-need-lodash-underscore: 6.14.0 typescript: 5.4.5 transitivePeerDependencies: @@ -7577,6 +8354,21 @@ snapshots: - supports-color - typescript + '@anolilab/semantic-release-preset@8.0.3(semantic-release@23.1.1(typescript@5.4.5))': + dependencies: + '@anolilab/package-json-utils': 3.0.9 + '@semantic-release/changelog': 6.0.3(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/commit-analyzer': 11.1.0(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/exec': 6.0.3(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/git': 10.0.1(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/github': 9.2.6(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/npm': 11.0.3(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/release-notes-generator': 12.1.0(semantic-release@23.1.1(typescript@5.4.5)) + conventional-changelog-conventionalcommits: 7.0.2 + semantic-release: 23.1.1(typescript@5.4.5) + transitivePeerDependencies: + - supports-color + '@anolilab/textlint-config@8.0.16(textlint@14.0.4)': dependencies: '@anolilab/package-json-utils': 3.0.9 @@ -7757,6 +8549,8 @@ snapshots: '@babel/helper-validator-identifier': 7.24.5 to-fast-properties: 2.0.0 + '@balena/dockerignore@1.0.2': {} + '@bcoe/v8-coverage@0.2.3': {} '@colors/colors@1.5.0': @@ -8117,72 +8911,141 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@esbuild/aix-ppc64@0.19.12': + optional: true + '@esbuild/aix-ppc64@0.20.2': optional: true + '@esbuild/android-arm64@0.19.12': + optional: true + '@esbuild/android-arm64@0.20.2': optional: true + '@esbuild/android-arm@0.19.12': + optional: true + '@esbuild/android-arm@0.20.2': optional: true + '@esbuild/android-x64@0.19.12': + optional: true + '@esbuild/android-x64@0.20.2': optional: true + '@esbuild/darwin-arm64@0.19.12': + optional: true + '@esbuild/darwin-arm64@0.20.2': optional: true + '@esbuild/darwin-x64@0.19.12': + optional: true + '@esbuild/darwin-x64@0.20.2': optional: true + '@esbuild/freebsd-arm64@0.19.12': + optional: true + '@esbuild/freebsd-arm64@0.20.2': optional: true + '@esbuild/freebsd-x64@0.19.12': + optional: true + '@esbuild/freebsd-x64@0.20.2': optional: true + '@esbuild/linux-arm64@0.19.12': + optional: true + '@esbuild/linux-arm64@0.20.2': optional: true + '@esbuild/linux-arm@0.19.12': + optional: true + '@esbuild/linux-arm@0.20.2': optional: true + '@esbuild/linux-ia32@0.19.12': + optional: true + '@esbuild/linux-ia32@0.20.2': optional: true + '@esbuild/linux-loong64@0.19.12': + optional: true + '@esbuild/linux-loong64@0.20.2': optional: true + '@esbuild/linux-mips64el@0.19.12': + optional: true + '@esbuild/linux-mips64el@0.20.2': optional: true + '@esbuild/linux-ppc64@0.19.12': + optional: true + '@esbuild/linux-ppc64@0.20.2': optional: true + '@esbuild/linux-riscv64@0.19.12': + optional: true + '@esbuild/linux-riscv64@0.20.2': optional: true + '@esbuild/linux-s390x@0.19.12': + optional: true + '@esbuild/linux-s390x@0.20.2': optional: true + '@esbuild/linux-x64@0.19.12': + optional: true + '@esbuild/linux-x64@0.20.2': optional: true + '@esbuild/netbsd-x64@0.19.12': + optional: true + '@esbuild/netbsd-x64@0.20.2': optional: true + '@esbuild/openbsd-x64@0.19.12': + optional: true + '@esbuild/openbsd-x64@0.20.2': optional: true + '@esbuild/sunos-x64@0.19.12': + optional: true + '@esbuild/sunos-x64@0.20.2': optional: true + '@esbuild/win32-arm64@0.19.12': + optional: true + '@esbuild/win32-arm64@0.20.2': optional: true + '@esbuild/win32-ia32@0.19.12': + optional: true + '@esbuild/win32-ia32@0.20.2': optional: true + '@esbuild/win32-x64@0.19.12': + optional: true + '@esbuild/win32-x64@0.20.2': optional: true @@ -8303,7 +9166,7 @@ snapshots: dependencies: '@npmcli/map-workspaces': 3.0.4 ci-info: 3.8.0 - ini: 4.1.2 + ini: 4.1.3 nopt: 7.2.0 proc-log: 3.0.0 read-package-json-fast: 3.0.2 @@ -8314,7 +9177,7 @@ snapshots: dependencies: '@npmcli/map-workspaces': 3.0.4 ci-info: 4.0.0 - ini: 4.1.2 + ini: 4.1.3 nopt: 7.2.0 proc-log: 4.2.0 read-package-json-fast: 3.0.2 @@ -8412,6 +9275,8 @@ snapshots: '@octokit/auth-token@3.0.4': {} + '@octokit/auth-token@4.0.0': {} + '@octokit/auth-token@5.1.1': {} '@octokit/core@4.2.4': @@ -8426,6 +9291,16 @@ snapshots: transitivePeerDependencies: - encoding + '@octokit/core@5.2.0': + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.0 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.5.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + '@octokit/core@6.1.2': dependencies: '@octokit/auth-token': 5.1.1 @@ -8447,6 +9322,11 @@ snapshots: is-plain-object: 5.0.0 universal-user-agent: 6.0.1 + '@octokit/endpoint@9.0.5': + dependencies: + '@octokit/types': 13.5.0 + universal-user-agent: 6.0.1 + '@octokit/graphql@5.0.6': dependencies: '@octokit/request': 6.2.8 @@ -8455,6 +9335,12 @@ snapshots: transitivePeerDependencies: - encoding + '@octokit/graphql@7.1.0': + dependencies: + '@octokit/request': 8.4.0 + '@octokit/types': 13.5.0 + universal-user-agent: 6.0.1 + '@octokit/graphql@8.1.1': dependencies: '@octokit/request': 9.1.1 @@ -8463,6 +9349,8 @@ snapshots: '@octokit/openapi-types@18.1.1': {} + '@octokit/openapi-types@20.0.0': {} + '@octokit/openapi-types@22.2.0': {} '@octokit/plugin-paginate-rest@11.3.0(@octokit/core@6.1.2)': @@ -8476,12 +9364,24 @@ snapshots: '@octokit/tsconfig': 1.0.2 '@octokit/types': 9.3.2 + '@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.2.0)': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 + '@octokit/plugin-retry@4.1.6(@octokit/core@4.2.4)': dependencies: '@octokit/core': 4.2.4 '@octokit/types': 9.3.2 bottleneck: 2.19.5 + '@octokit/plugin-retry@6.0.1(@octokit/core@5.2.0)': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 12.6.0 + bottleneck: 2.19.5 + '@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 @@ -8495,6 +9395,12 @@ snapshots: '@octokit/types': 9.3.2 bottleneck: 2.19.5 + '@octokit/plugin-throttling@8.2.0(@octokit/core@5.2.0)': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 + bottleneck: 2.19.5 + '@octokit/plugin-throttling@9.3.0(@octokit/core@6.1.2)': dependencies: '@octokit/core': 6.1.2 @@ -8507,6 +9413,12 @@ snapshots: deprecation: 2.3.1 once: 1.4.0 + '@octokit/request-error@5.1.0': + dependencies: + '@octokit/types': 13.5.0 + deprecation: 2.3.1 + once: 1.4.0 + '@octokit/request-error@6.1.1': dependencies: '@octokit/types': 13.5.0 @@ -8522,6 +9434,13 @@ snapshots: transitivePeerDependencies: - encoding + '@octokit/request@8.4.0': + dependencies: + '@octokit/endpoint': 9.0.5 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.5.0 + universal-user-agent: 6.0.1 + '@octokit/request@9.1.1': dependencies: '@octokit/endpoint': 10.1.1 @@ -8531,6 +9450,10 @@ snapshots: '@octokit/tsconfig@1.0.2': {} + '@octokit/types@12.6.0': + dependencies: + '@octokit/openapi-types': 20.0.0 + '@octokit/types@13.5.0': dependencies: '@octokit/openapi-types': 22.2.0 @@ -8539,6 +9462,8 @@ snapshots: dependencies: '@octokit/openapi-types': 18.1.1 + '@one-ini/wasm@0.1.1': {} + '@phenomnomnominal/tsquery@5.0.1(typescript@5.4.5)': dependencies: esquery: 1.5.0 @@ -8716,6 +9641,19 @@ snapshots: lodash: 4.17.21 semantic-release: 23.1.1(typescript@5.4.5) + '@semantic-release/commit-analyzer@11.1.0(semantic-release@23.1.1(typescript@5.4.5))': + dependencies: + conventional-changelog-angular: 7.0.0 + conventional-commits-filter: 4.0.0 + conventional-commits-parser: 5.0.0 + debug: 4.3.4 + import-from-esm: 1.3.3 + lodash-es: 4.17.21 + micromatch: 4.0.5 + semantic-release: 23.1.1(typescript@5.4.5) + transitivePeerDependencies: + - supports-color + '@semantic-release/commit-analyzer@12.0.0(semantic-release@23.1.1(typescript@5.4.5))': dependencies: conventional-changelog-angular: 7.0.0 @@ -8794,6 +9732,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@semantic-release/github@10.0.5(semantic-release@23.1.1(typescript@5.4.5))': + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) + '@octokit/plugin-retry': 7.1.1(@octokit/core@6.1.2) + '@octokit/plugin-throttling': 9.3.0(@octokit/core@6.1.2) + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + debug: 4.3.4 + dir-glob: 3.0.1 + globby: 14.0.1 + http-proxy-agent: 7.0.0 + https-proxy-agent: 7.0.2 + issue-parser: 7.0.0 + lodash-es: 4.17.21 + mime: 4.0.0 + p-filter: 4.1.0 + semantic-release: 23.1.1(typescript@5.4.5) + url-join: 5.0.0 + transitivePeerDependencies: + - supports-color + '@semantic-release/github@8.1.0(semantic-release@23.1.1(typescript@5.4.5))': dependencies: '@octokit/core': 4.2.4 @@ -8818,6 +9778,45 @@ snapshots: - encoding - supports-color + '@semantic-release/github@9.2.6(semantic-release@23.1.1(typescript@5.4.5))': + dependencies: + '@octokit/core': 5.2.0 + '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.2.0) + '@octokit/plugin-retry': 6.0.1(@octokit/core@5.2.0) + '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.2.0) + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + debug: 4.3.4 + dir-glob: 3.0.1 + globby: 14.0.1 + http-proxy-agent: 7.0.0 + https-proxy-agent: 7.0.2 + issue-parser: 6.0.0 + lodash-es: 4.17.21 + mime: 4.0.0 + p-filter: 4.1.0 + semantic-release: 23.1.1(typescript@5.4.5) + url-join: 5.0.0 + transitivePeerDependencies: + - supports-color + + '@semantic-release/npm@11.0.3(semantic-release@23.1.1(typescript@5.4.5))': + dependencies: + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + execa: 8.0.1 + fs-extra: 11.1.1 + lodash-es: 4.17.21 + nerf-dart: 1.0.0 + normalize-url: 8.0.1 + npm: 10.7.0 + rc: 1.2.8 + read-pkg: 9.0.1 + registry-auth-token: 5.0.2 + semantic-release: 23.1.1(typescript@5.4.5) + semver: 7.6.2 + tempy: 3.1.0 + '@semantic-release/npm@12.0.1(semantic-release@23.1.1(typescript@5.4.5))': dependencies: '@semantic-release/error': 4.0.0 @@ -8868,6 +9867,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@semantic-release/release-notes-generator@12.1.0(semantic-release@23.1.1(typescript@5.4.5))': + dependencies: + conventional-changelog-angular: 7.0.0 + conventional-changelog-writer: 7.0.1 + conventional-commits-filter: 4.0.0 + conventional-commits-parser: 5.0.0 + debug: 4.3.4 + get-stream: 7.0.1 + import-from-esm: 1.3.3 + into-stream: 7.0.0 + lodash-es: 4.17.21 + read-pkg-up: 11.0.0 + semantic-release: 23.1.1(typescript@5.4.5) + transitivePeerDependencies: + - supports-color + '@semantic-release/release-notes-generator@13.0.0(semantic-release@23.1.1(typescript@5.4.5))': dependencies: conventional-changelog-angular: 7.0.0 @@ -8896,6 +9911,8 @@ snapshots: '@sindresorhus/is@5.6.0': {} + '@sindresorhus/is@6.3.1': {} + '@sindresorhus/merge-streams@1.0.0': {} '@sindresorhus/merge-streams@2.3.0': {} @@ -9270,16 +10287,27 @@ snapshots: '@types/concat-stream@2.0.0': dependencies: - '@types/node': 20.12.12 + '@types/node': 18.18.14 '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 20.12.12 + '@types/node': 18.18.14 '@types/debug@4.1.8': dependencies: '@types/ms': 0.7.31 + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 18.18.14 + '@types/ssh2': 1.15.0 + + '@types/dockerode@3.3.29': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 18.18.14 + '@types/ssh2': 1.15.0 + '@types/estree-jsx@1.0.0': dependencies: '@types/estree': 1.0.5 @@ -9298,6 +10326,10 @@ snapshots: '@types/http-cache-semantics@4.0.2': {} + '@types/http-cache-semantics@4.0.4': {} + + '@types/ini@4.1.0': {} + '@types/inquirer@9.0.7': dependencies: '@types/through': 0.0.33 @@ -9310,7 +10342,7 @@ snapshots: '@types/liftoff@4.0.3': dependencies: '@types/fined': 1.1.5 - '@types/node': 20.12.12 + '@types/node': 18.18.14 '@types/mdast@3.0.12': dependencies: @@ -9340,13 +10372,25 @@ snapshots: '@types/parse-json@4.0.2': {} + '@types/retry@0.12.2': {} + + '@types/semantic-release__error@3.0.3': {} + '@types/semver@7.5.8': {} + '@types/ssh2@1.15.0': + dependencies: + '@types/node': 18.18.14 + + '@types/stream-buffers@3.0.7': + dependencies: + '@types/node': 18.18.14 + '@types/supports-color@8.1.1': {} '@types/through@0.0.33': dependencies: - '@types/node': 20.12.12 + '@types/node': 18.18.14 '@types/unist@2.0.8': {} @@ -9461,7 +10505,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 - ts-api-utils: 1.0.3(typescript@5.4.5) + ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: @@ -9573,6 +10617,43 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@visulima/fs@2.1.1': + dependencies: + pathe: 1.1.2 + type-fest: 4.18.2 + + '@visulima/package@1.8.1': + dependencies: + '@visulima/fs': 2.1.1 + jsonc-parser: 3.2.1 + normalize-package-data: 6.0.1 + pathe: 1.1.2 + resolve-pkg-maps: 1.0.0 + type-fest: 4.18.2 + transitivePeerDependencies: + - yaml + + '@visulima/path@1.0.0': {} + + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0))': + dependencies: + '@ampproject/remapping': 2.2.1 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.4 + istanbul-reports: 3.1.6 + magic-string: 0.30.10 + magicast: 0.3.4 + picocolors: 1.0.1 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 6.0.0 + vitest: 1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0))': dependencies: '@ampproject/remapping': 2.2.1 @@ -9759,6 +10840,11 @@ snapshots: any-promise@1.3.0: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + arg@4.1.3: {} argparse@1.0.10: @@ -9799,6 +10885,10 @@ snapshots: arrify@1.0.1: {} + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + assertion-error@1.1.0: {} assign-symbols@1.0.0: {} @@ -9846,12 +10936,18 @@ snapshots: base64-js@1.5.1: {} + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + before-after-hook@2.2.3: {} before-after-hook@3.0.2: {} big-integer@1.6.51: {} + binary-extensions@2.3.0: {} + binaryextensions@4.18.0: {} bl@4.1.0: @@ -9919,6 +11015,9 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buildcheck@0.0.6: + optional: true + builtin-modules@3.3.0: {} builtins@5.0.1: @@ -9929,6 +11028,11 @@ snapshots: dependencies: run-applescript: 5.0.0 + bundle-require@4.1.0(esbuild@0.19.12): + dependencies: + esbuild: 0.19.12 + load-tsconfig: 0.2.5 + cac@6.7.14: {} cacheable-lookup@7.0.0: {} @@ -9943,6 +11047,16 @@ snapshots: normalize-url: 8.0.1 responselike: 3.0.0 + cacheable-request@12.0.1: + dependencies: + '@types/http-cache-semantics': 4.0.4 + get-stream: 9.0.1 + http-cache-semantics: 4.1.1 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.0.1 + responselike: 3.0.0 + cachedir@2.3.0: {} call-bind@1.0.2: @@ -10058,6 +11172,20 @@ snapshots: dependencies: get-func-name: 2.0.2 + chokidar@3.6.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.3 + + chownr@1.1.4: {} + chrono-node@2.7.5: dependencies: dayjs: 1.11.10 @@ -10169,10 +11297,14 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} + commander@11.1.0: {} commander@2.20.3: {} + commander@4.1.1: {} + comment-parser@1.4.0: {} commitizen@4.3.0(@types/node@18.18.14)(typescript@5.4.5): @@ -10386,6 +11518,12 @@ snapshots: optionalDependencies: typescript: 5.4.5 + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.6 + nan: 2.19.0 + optional: true + create-require@1.1.1: {} cross-env@7.0.3: @@ -10607,6 +11745,23 @@ snapshots: dependencies: path-type: 4.0.0 + docker-modem@5.0.3: + dependencies: + debug: 4.3.4 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.15.0 + transitivePeerDependencies: + - supports-color + + dockerode@4.0.2: + dependencies: + '@balena/dockerignore': 1.0.2 + docker-modem: 5.0.3 + tar-fs: 2.0.1 + transitivePeerDependencies: + - supports-color + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -10662,6 +11817,13 @@ snapshots: eastasianwidth@0.2.0: {} + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.6.2 + ejs@3.1.10: dependencies: jake: 10.9.1 @@ -10794,6 +11956,32 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + esbuild@0.20.2: optionalDependencies: '@esbuild/aix-ppc64': 0.20.2 @@ -10954,6 +12142,14 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-editorconfig@4.0.3: + dependencies: + editorconfig: 1.0.4 + eslint: 8.57.0 + klona: 2.0.6 + transitivePeerDependencies: + - supports-color + eslint-plugin-es-x@7.6.0(eslint@8.57.0): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) @@ -11172,6 +12368,16 @@ snapshots: eslint-plugin-vitest-globals@1.5.0: {} + eslint-plugin-vitest@0.4.1(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0)): + dependencies: + '@typescript-eslint/utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + optionalDependencies: + vitest: 1.6.0(@types/node@18.18.14)(@vitest/ui@1.6.0) + transitivePeerDependencies: + - supports-color + - typescript + eslint-plugin-vitest@0.4.1(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0)): dependencies: '@typescript-eslint/utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) @@ -11558,6 +12764,8 @@ snapshots: form-data-encoder@2.1.4: {} + form-data-encoder@4.0.2: {} + form-data@4.0.0: dependencies: asynckit: 0.4.0 @@ -11850,6 +13058,20 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 + got@14.3.0: + dependencies: + '@sindresorhus/is': 6.3.1 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 12.0.1 + decompress-response: 6.0.0 + form-data-encoder: 4.0.2 + get-stream: 8.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 4.0.1 + responselike: 3.0.0 + graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -12019,6 +13241,11 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + https-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 @@ -12093,6 +13320,8 @@ snapshots: ini@4.1.2: {} + ini@4.1.3: {} + inquirer@8.2.5: dependencies: ansi-escapes: 4.3.2 @@ -12186,6 +13415,10 @@ snapshots: dependencies: has-bigints: 1.0.2 + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-boolean-object@1.1.2: dependencies: call-bind: 1.0.7 @@ -12285,6 +13518,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-network-error@1.1.0: {} + is-npm@6.0.0: {} is-number-object@1.0.7: @@ -12484,6 +13719,8 @@ snapshots: jju@1.4.0: {} + joycon@3.1.1: {} + js-tokens@4.0.0: {} js-tokens@9.0.0: {} @@ -12534,6 +13771,8 @@ snapshots: jsonc-parser@3.2.0: {} + jsonc-parser@3.2.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -12552,12 +13791,18 @@ snapshots: dependencies: json-buffer: 3.0.1 + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kind-of@6.0.3: {} kleur@3.0.3: {} kleur@4.1.5: {} + klona@2.0.6: {} + latest-version@7.0.0: dependencies: package-json: 8.1.1 @@ -12635,6 +13880,8 @@ snapshots: '@npmcli/config': 8.3.1 import-meta-resolve: 4.0.0 + load-tsconfig@0.2.5: {} + local-pkg@0.5.0: dependencies: mlly: 1.4.2 @@ -13742,6 +14989,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 @@ -13766,6 +15017,8 @@ snapshots: misspellings@1.1.0: {} + mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -13783,6 +15036,10 @@ snapshots: moment@2.29.4: {} + move-file@3.1.0: + dependencies: + path-exists: 5.0.0 + mri@1.2.0: {} mrmime@2.0.0: {} @@ -13801,6 +15058,9 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nan@2.19.0: + optional: true + nanoid@3.3.7: {} natural-compare-lite@1.4.0: {} @@ -13919,6 +15179,15 @@ snapshots: semver: 7.6.2 validate-npm-package-license: 3.0.4 + normalize-package-data@6.0.1: + dependencies: + hosted-git-info: 7.0.1 + is-core-module: 2.13.0 + semver: 7.6.2 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + normalize-url@6.1.0: {} normalize-url@8.0.1: {} @@ -14111,6 +15380,8 @@ snapshots: p-cancelable@3.0.0: {} + p-cancelable@4.0.1: {} + p-defer@1.0.0: {} p-each-series@3.0.0: {} @@ -14191,6 +15462,12 @@ snapshots: p-reduce@3.0.0: {} + p-retry@6.2.0: + dependencies: + '@types/retry': 0.12.2 + is-network-error: 1.1.0 + retry: 0.13.1 + p-timeout@3.2.0: dependencies: p-finally: 1.0.0 @@ -14376,6 +15653,8 @@ snapshots: pathe@1.1.1: {} + pathe@1.1.2: {} + pathval@1.1.1: {} pause-stream@0.0.11: @@ -14400,6 +15679,8 @@ snapshots: pinkie@2.0.4: {} + pirates@4.0.6: {} + pkg-conf@2.1.0: dependencies: find-up: 2.1.0 @@ -14428,10 +15709,18 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.18.14)(typescript@5.4.5)): + dependencies: + lilconfig: 3.0.0 + yaml: 2.3.4 + optionalDependencies: + postcss: 8.4.38 + ts-node: 10.9.2(@types/node@18.18.14)(typescript@5.4.5) + postcss@8.4.38: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 + picocolors: 1.0.1 source-map-js: 1.2.0 prelude-ls@1.2.1: {} @@ -14492,6 +15781,11 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 + pump@3.0.0: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + punycode@2.3.0: {} pupa@3.1.0: @@ -14635,6 +15929,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + readline-transform@1.0.0: {} rechoir@0.8.0: @@ -14896,6 +16194,8 @@ snapshots: unified: 10.1.2 unist-util-position: 4.0.4 + retry@0.13.1: {} + reusify@1.0.4: {} rfdc@1.3.0: {} @@ -14986,6 +16286,19 @@ snapshots: refa: 0.12.1 regexp-ast-analysis: 0.7.1 + secretlint@8.1.2: + dependencies: + '@secretlint/config-creator': 8.2.4 + '@secretlint/formatter': 8.2.4 + '@secretlint/node': 8.2.4 + '@secretlint/profiler': 8.2.4 + debug: 4.3.4 + globby: 14.0.1 + meow: 12.1.1 + read-pkg: 8.1.0 + transitivePeerDependencies: + - supports-color + secretlint@8.2.3: dependencies: '@secretlint/config-creator': 8.2.4 @@ -15195,6 +16508,10 @@ snapshots: source-map@0.6.1: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + space-separated-tokens@2.0.2: {} spawn-error-forwarder@1.0.0: {} @@ -15218,6 +16535,8 @@ snapshots: spdx-license-ids@3.0.15: {} + split-ca@1.0.1: {} + split-transform-stream@0.1.1: dependencies: bubble-stream-error: 0.0.1 @@ -15244,6 +16563,14 @@ snapshots: sprintf-js@1.0.3: {} + ssh2@1.15.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.19.0 + stackback@0.0.2: {} std-env@3.7.0: {} @@ -15378,6 +16705,16 @@ snapshots: dependencies: boundary: 2.0.0 + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.3.15 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + super-regex@1.0.0: dependencies: function-timeout: 1.0.1 @@ -15433,6 +16770,13 @@ snapshots: tapable@2.2.1: {} + tar-fs@2.0.1: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -15714,12 +17058,18 @@ snapshots: tr46@0.0.3: {} + tr46@1.0.1: + dependencies: + punycode: 2.3.0 + traverse@0.6.9: dependencies: gopd: 1.0.1 typedarray.prototype.slice: 1.0.3 which-typed-array: 1.1.15 + tree-kill@1.2.2: {} + trim-newlines@3.0.1: {} trim-newlines@4.1.1: {} @@ -15738,6 +17088,10 @@ snapshots: dependencies: typescript: 5.4.5 + ts-deepmerge@7.0.0: {} + + ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@types/node@18.18.14)(typescript@4.9.5): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -15756,6 +17110,25 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-node@10.9.2(@types/node@18.18.14)(typescript@5.4.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 18.18.14 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.4.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -15766,6 +17139,29 @@ snapshots: tslib@2.6.2: {} + tsup@8.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.18.14)(typescript@5.4.5))(typescript@5.4.5): + dependencies: + bundle-require: 4.1.0(esbuild@0.19.12) + cac: 6.7.14 + chokidar: 3.6.0 + debug: 4.3.4 + esbuild: 0.19.12 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@18.18.14)(typescript@5.4.5)) + resolve-from: 5.0.0 + rollup: 4.14.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.4.38 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + - ts-node + tsutils-etc@1.4.2(tsutils@3.21.0(typescript@5.4.5))(typescript@5.4.5): dependencies: '@types/yargs': 17.0.32 @@ -15778,6 +17174,8 @@ snapshots: tslib: 1.14.1 typescript: 5.4.5 + tweetnacl@0.14.5: {} + txt-ast-traverse@1.2.1: {} type-check@0.4.0: @@ -15804,6 +17202,8 @@ snapshots: type-fest@3.13.1: {} + type-fest@4.18.2: {} + type-fest@4.8.3: {} typed-array-buffer@1.0.2: @@ -16136,7 +17536,7 @@ snapshots: dependencies: browserslist: 4.23.0 escalade: 3.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 update-notifier@6.0.2: dependencies: @@ -16281,7 +17681,7 @@ snapshots: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 vite: 5.2.8(@types/node@18.18.14) transitivePeerDependencies: - '@types/node' @@ -16341,8 +17741,8 @@ snapshots: execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 - pathe: 1.1.1 - picocolors: 1.0.0 + pathe: 1.1.2 + picocolors: 1.0.1 std-env: 3.7.0 strip-literal: 2.1.0 tinybench: 2.5.1 @@ -16408,11 +17808,19 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4